├── _config.yml
├── .gitattributes
├── .travis
├── znode-deploy.sh
└── znode_deploy.xml
├── repo
└── com
│ └── massivecraft
│ ├── mcore
│ ├── 2.12.0
│ │ ├── mcore-2.12.0.jar
│ │ └── mcore-2.12.0.pom
│ └── maven-metadata.xml
│ └── factions
│ ├── 2.12.0
│ ├── factions-2.12.0.jar
│ └── factions-2.12.0.pom
│ └── maven-metadata.xml
├── src
├── main
│ ├── java
│ │ └── org
│ │ │ └── gestern
│ │ │ └── gringotts
│ │ │ ├── dependency
│ │ │ ├── GenericHandler.java
│ │ │ ├── DependencyHandler.java
│ │ │ ├── Dependency.java
│ │ │ ├── WorldGuardHandler.java
│ │ │ ├── FactionsHandler.java
│ │ │ └── TownyHandler.java
│ │ │ ├── GringottsException.java
│ │ │ ├── GringottsStorageException.java
│ │ │ ├── api
│ │ │ ├── PlayerAccount.java
│ │ │ ├── TaxedTransaction.java
│ │ │ ├── Transaction.java
│ │ │ ├── TransactionResult.java
│ │ │ ├── Currency.java
│ │ │ ├── BankAccount.java
│ │ │ ├── impl
│ │ │ │ ├── GringottsTaxedTransaction.java
│ │ │ │ ├── GringottsTransaction.java
│ │ │ │ └── GringottsEco.java
│ │ │ ├── Eco.java
│ │ │ └── Account.java
│ │ │ ├── GringottsConfigurationException.java
│ │ │ ├── accountholder
│ │ │ ├── AccountHolderProvider.java
│ │ │ ├── AccountHolder.java
│ │ │ ├── PlayerAccountHolder.java
│ │ │ └── AccountHolderFactory.java
│ │ │ ├── commands
│ │ │ ├── GringottsExecutor.java
│ │ │ ├── MoneyExecutor.java
│ │ │ ├── MoneyadminExecutor.java
│ │ │ └── GringottsAbstractExecutor.java
│ │ │ ├── event
│ │ │ ├── PlayerVaultCreationEvent.java
│ │ │ ├── PlayerVaultListener.java
│ │ │ ├── VaultCreator.java
│ │ │ ├── AccountListener.java
│ │ │ └── VaultCreationEvent.java
│ │ │ ├── currency
│ │ │ ├── DenominationKey.java
│ │ │ ├── Denomination.java
│ │ │ └── GringottsCurrency.java
│ │ │ ├── Permissions.java
│ │ │ ├── data
│ │ │ ├── EBeanAccount.java
│ │ │ ├── EBeanAccountChest.java
│ │ │ ├── DAO.java
│ │ │ └── EBeanDAO.java
│ │ │ ├── Accounting.java
│ │ │ ├── AccountInventory.java
│ │ │ ├── Util.java
│ │ │ ├── AccountChest.java
│ │ │ ├── Language.java
│ │ │ └── GringottsAccount.java
│ └── resources
│ │ └── i18n
│ │ └── messages_de.yml
└── test
│ ├── resources
│ └── testcases.md
│ └── java
│ └── org
│ └── gestern
│ └── gringotts
│ └── UtilTest.java
├── .travis.yml
├── config.yml
├── LICENSE.txt
├── plugin.yml
├── messages.yml
├── doc
├── usage.md
└── configuration.md
├── .gitignore
├── README.md
└── pom.xml
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-slate
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
--------------------------------------------------------------------------------
/.travis/znode-deploy.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | mvn -DskipTests=true deploy --settings .travis/znode_deploy.xml
4 |
--------------------------------------------------------------------------------
/repo/com/massivecraft/mcore/2.12.0/mcore-2.12.0.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MinecraftWars/Gringotts/HEAD/repo/com/massivecraft/mcore/2.12.0/mcore-2.12.0.jar
--------------------------------------------------------------------------------
/repo/com/massivecraft/factions/2.12.0/factions-2.12.0.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MinecraftWars/Gringotts/HEAD/repo/com/massivecraft/factions/2.12.0/factions-2.12.0.jar
--------------------------------------------------------------------------------
/repo/com/massivecraft/mcore/maven-metadata.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | com.massivecraft
4 | mcore
5 | 2.12.0
6 |
7 | 2.12.0
8 | 2.12.0
9 |
10 | 2.12.0
11 |
12 | 20171306235600
13 |
14 |
--------------------------------------------------------------------------------
/repo/com/massivecraft/factions/maven-metadata.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | com.massivecraft
4 | factions
5 | 2.12.0
6 |
7 | 2.12.0
8 | 2.12.0
9 |
10 | 2.12.0
11 |
12 | 20171306235600
13 |
14 |
--------------------------------------------------------------------------------
/.travis/znode_deploy.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | znode-releases
5 | ${env.ZNODE_USER}
6 | ${env.ZNODE_PASS}
7 |
8 |
9 | znode-snapshots
10 | ${env.ZNODE_USER}
11 | ${env.ZNODE_PASS}
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/repo/com/massivecraft/mcore/2.12.0/mcore-2.12.0.pom:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 | com.massivecraft
6 | mcore
7 | 2.12.0
8 | Artifactory auto generated POM
9 |
--------------------------------------------------------------------------------
/repo/com/massivecraft/factions/2.12.0/factions-2.12.0.pom:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 | com.massivecraft
6 | factions
7 | 2.12.0
8 | Artifactory auto generated POM
9 |
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/dependency/GenericHandler.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts.dependency;
2 |
3 | import org.bukkit.plugin.Plugin;
4 |
5 | public class GenericHandler implements DependencyHandler {
6 |
7 | private final Plugin plugin;
8 |
9 | public GenericHandler(Plugin plugin) {
10 | this.plugin = plugin;
11 | }
12 |
13 | @Override
14 | public boolean enabled() {
15 | return plugin != null && plugin.isEnabled();
16 | }
17 |
18 | @Override
19 | public boolean exists() {
20 | return plugin != null;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/GringottsException.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts;
2 |
3 | public class GringottsException extends RuntimeException {
4 |
5 | private static final long serialVersionUID = 476895381491480536L;
6 |
7 | public GringottsException() {
8 | super();
9 | }
10 |
11 | public GringottsException(String message, Throwable cause) {
12 | super(message, cause);
13 | }
14 |
15 | public GringottsException(String message) {
16 | super(message);
17 | }
18 |
19 | public GringottsException(Throwable cause) {
20 | super(cause);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/GringottsStorageException.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts;
2 |
3 | public class GringottsStorageException extends RuntimeException {
4 |
5 | private static final long serialVersionUID = -7762154730712697492L;
6 |
7 | public GringottsStorageException() {
8 | super();
9 | }
10 |
11 | public GringottsStorageException(String message, Throwable cause) {
12 | super(message, cause);
13 | }
14 |
15 | public GringottsStorageException(String message) {
16 | super(message);
17 | }
18 |
19 | public GringottsStorageException(Throwable cause) {
20 | super(cause);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/api/PlayerAccount.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts.api;
2 |
3 | public interface PlayerAccount extends Account {
4 |
5 | /**
6 | * Deposit an amount from the inventory or on-hand storage to the offline/chest storage.
7 | *
8 | * @param value value to deposit
9 | * @return Result of transaction
10 | */
11 | TransactionResult deposit(double value);
12 |
13 | /**
14 | * Withdraw an amount from offline/chest storage to inventory or on-hand money.
15 | *
16 | * @param value amount to withdraw
17 | * @return Result of transaction
18 | */
19 | TransactionResult withdraw(double value);
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/GringottsConfigurationException.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts;
2 |
3 | class GringottsConfigurationException extends RuntimeException {
4 |
5 | private static final long serialVersionUID = -2916461691910235253L;
6 |
7 | public GringottsConfigurationException() {
8 | super();
9 | }
10 |
11 | public GringottsConfigurationException(String message, Throwable cause) {
12 | super(message, cause);
13 | }
14 |
15 | public GringottsConfigurationException(String message) {
16 | super(message);
17 | }
18 |
19 | public GringottsConfigurationException(Throwable cause) {
20 | super(cause);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/api/TaxedTransaction.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts.api;
2 |
3 | public interface TaxedTransaction extends Transaction {
4 |
5 | /**
6 | * Add a tax collector to this taxed transaction. The tax collector account receives the taxes from this
7 | * transaction.
8 | *
9 | * @param taxCollector account to receive the taxes.
10 | * @return taxed transaction with tax collector
11 | */
12 | TaxedTransaction collectedBy(Account taxCollector);
13 |
14 | /**
15 | * Return the amount of taxes to be paid in this transaction.
16 | *
17 | * @return the amount of taxes to be paid in this transaction.
18 | */
19 | double tax();
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/api/Transaction.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts.api;
2 |
3 | public interface Transaction {
4 |
5 | /**
6 | * Complete the transaction by sending the transaction amount to a given account.
7 | *
8 | * @param to Account to which receives the value of this transaction.
9 | * @return result of the transaction.
10 | */
11 | TransactionResult to(Account to);
12 |
13 | /**
14 | * Apply taxes to this transaction, as configured by the economy plugin.
15 | * Completing the transaction will fail if the taxes cannot be collected.
16 | *
17 | * @return transaction with applied taxes
18 | */
19 | TaxedTransaction withTaxes();
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/accountholder/AccountHolderProvider.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts.accountholder;
2 |
3 |
4 | /**
5 | * Provides AccountHolder objects for a given id.
6 | * An AccountHolderProvider has its own internal mapping of ids to account holders.
7 | * For example, a Factions provider would return a FactionAccountHolder object when given the faction's id.
8 | *
9 | * @author jast
10 | */
11 | public interface AccountHolderProvider {
12 |
13 | /**
14 | * Get the AccountHolder object mapped to the given id for this provider.
15 | *
16 | * @param id id of account holder
17 | * @return account holder for id
18 | */
19 | AccountHolder getAccountHolder(String id);
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/api/TransactionResult.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts.api;
2 |
3 | /**
4 | * The result of a transaction.
5 | *
6 | * @author jast
7 | */
8 | public enum TransactionResult {
9 | /**
10 | * Transaction was successful.
11 | */
12 | SUCCESS,
13 | /**
14 | * Transaction failed due to insufficient funds of an account that money was to be taken from.
15 | */
16 | INSUFFICIENT_FUNDS,
17 | /**
18 | * Transaction failed due to insufficient space in an account that money was to be deposited to.
19 | */
20 | INSUFFICIENT_SPACE,
21 | /**
22 | * Transaction operation not supported.
23 | */
24 | UNSUPPORTED,
25 | /**
26 | * An error occurred while trying to process the transaction.
27 | */
28 | ERROR
29 | }
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/commands/GringottsExecutor.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts.commands;
2 |
3 | import org.bukkit.command.Command;
4 | import org.bukkit.command.CommandExecutor;
5 | import org.bukkit.command.CommandSender;
6 | import org.gestern.gringotts.Gringotts;
7 |
8 | import static org.gestern.gringotts.Language.LANG;
9 |
10 | /**
11 | * Administrative commands not related to ingame money.
12 | */
13 | public class GringottsExecutor extends GringottsAbstractExecutor {
14 |
15 | @Override
16 | public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args) {
17 | if (args.length >= 1 && "reload".equalsIgnoreCase(args[0])) {
18 | plugin.reloadConfig();
19 | sender.sendMessage(LANG.reload);
20 | return true;
21 | }
22 | return false;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/dependency/DependencyHandler.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts.dependency;
2 |
3 | /**
4 | * A DependencyHandler contains methods that deal with a specific Bukkit plugin dependency.
5 | * All interactions with any other plugin's API should happen through a handler, after checking that is is enabled.
6 | * This ensures that no unloaded code will be called.
7 | *
8 | * @author jast
9 | */
10 | public interface DependencyHandler {
11 |
12 | /**
13 | * Return whether the plugin handled by this handler is enabled.
14 | *
15 | * @return whether the plugin handled by this handler is enabled.
16 | */
17 | boolean enabled();
18 |
19 | /**
20 | * Return whether the dependency is loaded into classpath.
21 | *
22 | * @return whether the dependency is loaded into classpath.
23 | */
24 | boolean exists();
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/api/Currency.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts.api;
2 |
3 | public interface Currency {
4 |
5 | /**
6 | * Singular name of this currency.
7 | *
8 | * @return singular name of this currency
9 | */
10 | String name();
11 |
12 | /**
13 | * Plural name of this currency.
14 | *
15 | * @return plural name of this currency
16 | */
17 | String namePlural();
18 |
19 | /**
20 | * Get a formatted currency value.
21 | * The resulting string includes the amount as well as currency name or name of individual denominations.
22 | *
23 | * @param value value to format.
24 | * @return the formatted currency value.
25 | */
26 | String format(double value);
27 |
28 | /**
29 | * Get the amount of fractional digits supported by this currency.
30 | *
31 | * @return the amount of fractional digits supported by this currency
32 | */
33 | int fractionalDigits();
34 | }
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/event/PlayerVaultCreationEvent.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts.event;
2 |
3 | import org.bukkit.event.HandlerList;
4 | import org.bukkit.event.block.SignChangeEvent;
5 |
6 | /**
7 | * Vault creation event triggered by a player.
8 | *
9 | * @author jast
10 | */
11 | public class PlayerVaultCreationEvent extends VaultCreationEvent {
12 |
13 | private final SignChangeEvent cause;
14 |
15 | public PlayerVaultCreationEvent(Type type, SignChangeEvent cause) {
16 | super(type);
17 |
18 | this.cause = cause;
19 | }
20 |
21 | /**
22 | * Get the player involved in creating the vault.
23 | *
24 | * @return the player involved in creating the vault
25 | */
26 | public SignChangeEvent getCause() {
27 | return cause;
28 | }
29 |
30 | public static HandlerList getHandlerList() {
31 | return VaultCreationEvent.handlers;
32 | // TODO ensure we can actually have superclass handle these safely
33 | // TODO find out what I meant by that?
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/currency/DenominationKey.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts.currency;
2 |
3 | import org.bukkit.inventory.ItemStack;
4 |
5 | /**
6 | * Hashable information to identify a denomination by it's ItemStack.
7 | */
8 | public class DenominationKey {
9 |
10 | /**
11 | * Item type of this denomination.
12 | */
13 | public final ItemStack type;
14 |
15 |
16 | /**
17 | * Create a denomination key based on an item stack.
18 | *
19 | * @param type item type of denomination
20 | */
21 | public DenominationKey(ItemStack type) {
22 | this.type = new ItemStack(type);
23 | this.type.setAmount(0);
24 | }
25 |
26 | @Override
27 | public boolean equals(Object o) {
28 | if (this == o) return true;
29 | if (o == null || getClass() != o.getClass()) return false;
30 |
31 | DenominationKey that = (DenominationKey) o;
32 |
33 | return type.equals(that.type);
34 |
35 | }
36 |
37 | @Override
38 | public int hashCode() {
39 | return type.hashCode();
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 |
3 | jdk:
4 | - openjdk8
5 |
6 | notifications:
7 | webhooks:
8 | urls:
9 | - https://webhooks.gitter.im/e/9d082586e459ec0fb134
10 | on_success: change # options: [always|never|change] default: always
11 | on_failure: always # options: [always|never|change] default: always
12 | on_start: false # default: false
13 |
14 | deploy:
15 |
16 | - provider: script
17 | skip_cleanup: true
18 | script: .travis/znode-deploy.sh
19 | on:
20 | branch: master
21 |
22 | - provider: releases
23 | skip_cleanup: true
24 | api_key:
25 | secure: GqBVV6OCdr6n6KSaq14X+2f2+I1mTwPqpQ7L6sdPUZUD/ziXA4rySZ9R3MYYxZWo/SOvAzX5ab8QpJSGnPoXZncL7dxFmO3FfE0WMHy74FCinuRWQ2ziBK4TEJBOVGfJqTvyutmCXLniw5G8uQxozkEUpjKJm3i1bjA+7X+7qlI=
26 | file_glob: true
27 | file: target/Gringotts*.jar
28 | on:
29 | repo: MinecraftWars/Gringotts
30 | tags: true
31 |
32 | # we don't need sudo, this allows container-based builds ...
33 | sudo: false
34 |
35 | # ... which allows caching of .m2 and makes builds faster
36 | cache:
37 | directories:
38 | - $HOME/.m2
39 |
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/api/BankAccount.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts.api;
2 |
3 | public interface BankAccount extends Account {
4 |
5 | /**
6 | * Add an owner to this bank account.
7 | *
8 | * @param player Player to add as owner to this bank account.
9 | * @return This bank account with the owner added.
10 | */
11 | BankAccount addOwner(String player);
12 |
13 | /**
14 | * Add a member to this bank account.
15 | *
16 | * @param player Player to add as owner to this bank account.
17 | * @return This bank account with the member added.
18 | */
19 | BankAccount addMember(String player);
20 |
21 | /**
22 | * Return whether a player is an owner of this bank account.
23 | *
24 | * @param player player to check for ownership
25 | * @return whether a player is an owner of this bank account.
26 | */
27 | boolean isOwner(String player);
28 |
29 | /**
30 | * Return whether a player is a member of this bank account.
31 | *
32 | * @param player player to check for membership
33 | * @return whether a player is an member of this bank account.
34 | */
35 | boolean isMember(String player);
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/Permissions.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts;
2 |
3 | import org.bukkit.entity.Player;
4 |
5 | public enum Permissions {
6 | USEVAULT_INVENTORY("gringotts.usevault.inventory"),
7 | USEVAULT_ENDERCHEST("gringotts.usevault.enderchest"),
8 |
9 | CREATEVAULT_ADMIN("gringotts.createvault.admin"),
10 | CREATEVAULT_PLAYER("gringotts.createvault.player"),
11 | CREATEVAULT_FACTION("gringotts.createvault.faction"),
12 | CREATEVAULT_TOWN("gringotts.createvault.town"),
13 | CREATEVAULT_NATION("gringotts.createvault.nation"),
14 | CREATEVAULT_WORLDGUARD("gringotts.createvault.worldguard"),
15 |
16 | TRANSFER("gringotts.transfer"),
17 | COMMAND_WITHDRAW("gringotts.command.withdraw"),
18 | COMMAND_DEPOSIT("gringotts.command.deposit");
19 |
20 | public final String node;
21 |
22 | Permissions(String node) {
23 | this.node = node;
24 | }
25 |
26 | /**
27 | * Check if a player has this permission.
28 | *
29 | * @param player player to check
30 | * @return whether given player has this permission
31 | */
32 | public boolean allowed(Player player) {
33 | return player.hasPermission(this.node);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/config.yml:
--------------------------------------------------------------------------------
1 | # for details on configuration, see https://github.com/MinecraftWars/Gringotts/blob/master/doc/configuration.md
2 |
3 | # supported languages: "custom" (default, english) and "de" (german)
4 | language: custom
5 |
6 | # currency name and value config
7 | currency:
8 | # currency name to use in messages
9 | name:
10 | singular: Emerald
11 | plural: Emeralds
12 |
13 | # number of decimal digits supported by currency value (0 for whole numbers only)
14 | digits: 2
15 |
16 | # Display account balances with individual denomination names
17 | named-denominations: false
18 |
19 | # value of individual denominations. default: emerald: 1, emerald block: 9
20 | denominations:
21 | - material: emerald
22 | value: 1
23 | - material: emerald_block
24 | value: 9
25 |
26 | # tax on /money pay transactions
27 | transactiontax:
28 | flat: 0.0
29 | rate: 0.0
30 |
31 | # balance to start an account with (purely virtual)
32 | startingbalance:
33 | player: 0
34 | faction: 0
35 | town: 0
36 | nation: 0
37 |
38 | # globally (dis)allow use of vault types
39 | usevault:
40 | container: true
41 | enderchest: true
42 |
43 | # whether money/balance commands show vault and inventory balance separately
44 | balance:
45 | show-vault: true
46 | show-inventory: true
47 |
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/accountholder/AccountHolder.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts.accountholder;
2 |
3 |
4 | /**
5 | * An account holder.
6 | * Can be a player or another type of entity able to participate in the economy, for instance a faction.
7 | *
8 | * To function correctly within Gringotts, implementors must provide a working equals and hashCode method.
9 | *
10 | * @author jast
11 | */
12 | public interface AccountHolder {
13 | /**
14 | * Return name of the account holder.
15 | *
16 | * @return name of the account holder
17 | */
18 | String getName();
19 |
20 | /**
21 | * Send message to the account holder.
22 | *
23 | * @param message to send
24 | */
25 | void sendMessage(String message);
26 |
27 | @Override
28 | int hashCode();
29 |
30 | @Override
31 | boolean equals(Object other);
32 |
33 | /**
34 | * Type of the account holder. For instance "faction" or "player".
35 | *
36 | * @return account holder type
37 | */
38 | String getType();
39 |
40 | /**
41 | * A unique identifier for the account holder.
42 | * For players, this is simply the name. For factions, it is their id.
43 | *
44 | * @return unique account holder id
45 | */
46 | String getId();
47 | }
48 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012, Justin Kaeser, aka jasticE, aka ebenwert
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification,
5 | are permitted provided that the following conditions are met:
6 |
7 | * Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 | * Redistributions in binary form must reproduce the above copyright notice, this
10 | list of conditions and the following disclaimer in the documentation and/or
11 | other materials provided with the distribution.
12 |
13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
17 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
20 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
21 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
22 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 |
24 |
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/data/EBeanAccount.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts.data;
2 |
3 | import com.avaje.ebean.validation.NotNull;
4 |
5 | import javax.persistence.Entity;
6 | import javax.persistence.Id;
7 | import javax.persistence.Table;
8 | import javax.persistence.UniqueConstraint;
9 |
10 | @Entity
11 | @Table(name = "gringotts_account")
12 | @UniqueConstraint(columnNames = {"type", "owner"})
13 | public class EBeanAccount {
14 | @Id
15 | int id;
16 | /**
17 | * Type string.
18 | */
19 | @NotNull
20 | String type;
21 | /**
22 | * Owner id.
23 | */
24 | @NotNull
25 | String owner;
26 | /**
27 | * Virtual balance.
28 | */
29 | @NotNull
30 | long cents;
31 |
32 | public int getId() {
33 | return id;
34 | }
35 |
36 | public void setId(int id) {
37 | this.id = id;
38 | }
39 |
40 | public String getType() {
41 | return type;
42 | }
43 |
44 | public void setType(String type) {
45 | this.type = type;
46 | }
47 |
48 | public String getOwner() {
49 | return owner;
50 | }
51 |
52 | public void setOwner(String owner) {
53 | this.owner = owner;
54 | }
55 |
56 | public long getCents() {
57 | return cents;
58 | }
59 |
60 | public void setCents(long cents) {
61 | this.cents = cents;
62 | }
63 |
64 | @Override
65 | public String toString() {
66 | return "EBeanAccount(" + owner + "," + type + ": " + cents + ")";
67 | }
68 |
69 | }
--------------------------------------------------------------------------------
/src/test/resources/testcases.md:
--------------------------------------------------------------------------------
1 | Gringotts Manual Test Cases
2 | ===========================
3 |
4 | balance
5 | -------
6 |
7 | === setup
8 | before every test: empty inventory
9 |
10 | === 1
11 | add emerald directly
12 |
13 | /money
14 |
15 | verify that balance is 1
16 |
17 | === 2
18 | * add 10 emeralds
19 | * verify balance 10
20 |
21 | === 3
22 | * add stack of emeralds
23 | * verify balance 64
24 |
25 | === 4
26 | * add 2 stacks of emeralds
27 | * verify balance 128
28 |
29 | === 5
30 | * add emerald block
31 | * verify balance 9
32 |
33 | === 6
34 | * add stack of emerald blocks
35 | * verify balance 576
36 |
37 | add money
38 | ---------
39 |
40 | === 1
41 | /money
42 | /moneyadm add 1 player
43 | verify correct, repeat
44 |
45 | === 2
46 | /money
47 | /moneyadm add 9 player
48 | verify correct, repeat
49 |
50 | === 3
51 | /money
52 | /moneyadm add 10 player
53 | verify correct, repeat
54 |
55 | === 4
56 | /money
57 | /moneyadm add 1000 player
58 | verify correct, repeat
59 |
60 | === 5
61 | /money
62 | /moneyadm add 1000000 player
63 | should be more than capacity
64 | verify money unchanged
65 |
66 |
67 | remove money
68 | ------------
69 |
70 | === setup
71 | before every test
72 |
73 | /moneyadm add 1000
74 |
75 | === 1
76 | /money
77 | /moneyadm rm 1
78 | verify, repeat
79 |
80 | === 2
81 | /money
82 | /moneyadm rm 9
83 | verify, repeat
84 |
85 | === 3
86 | /money
87 | /moneyadm rm 10
88 | verify, repeat
89 |
90 | === 4
91 | /money
92 | /moneyadm rm 1000000
93 | verify balance unchanged
94 |
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/api/impl/GringottsTaxedTransaction.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts.api.impl;
2 |
3 | import org.gestern.gringotts.api.Account;
4 | import org.gestern.gringotts.api.TaxedTransaction;
5 | import org.gestern.gringotts.api.TransactionResult;
6 |
7 | import static org.gestern.gringotts.api.TransactionResult.SUCCESS;
8 |
9 | public class GringottsTaxedTransaction extends GringottsTransaction implements TaxedTransaction {
10 |
11 | /**
12 | * Taxes to apply to transaction.
13 | */
14 | private final double taxes;
15 |
16 | /**
17 | * Create taxed transaction, adding given amount of taxes to the given base transaction
18 | *
19 | * @param base transaction on which the tax is based
20 | * @param taxes taxes to apply to transaction
21 | */
22 | protected GringottsTaxedTransaction(GringottsTransaction base, double taxes) {
23 | super(base);
24 |
25 | this.taxes = taxes;
26 | }
27 |
28 | @Override
29 | public TransactionResult to(Account recipient) {
30 | TransactionResult taxResult = from.remove(taxes);
31 |
32 | if (taxResult != SUCCESS) {
33 | return taxResult;
34 | }
35 |
36 | TransactionResult result = super.to(recipient);
37 |
38 | // undo taxing if transaction failed
39 | if (result != SUCCESS) {
40 | from.add(taxes);
41 | }
42 |
43 | return result;
44 | }
45 |
46 | @Override
47 | public TaxedTransaction collectedBy(Account taxCollector) {
48 | throw new RuntimeException("tax collector account not yet implemented");
49 | }
50 |
51 | @Override
52 | public double tax() {
53 | return taxes;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/data/EBeanAccountChest.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts.data;
2 |
3 | import com.avaje.ebean.validation.NotNull;
4 |
5 | import javax.persistence.Entity;
6 | import javax.persistence.Id;
7 | import javax.persistence.Table;
8 | import javax.persistence.UniqueConstraint;
9 |
10 | @Entity
11 | @Table(name = "gringotts_accountchest")
12 | @UniqueConstraint(columnNames = {"world", "x", "y", "z"})
13 | public class EBeanAccountChest {
14 | @Id
15 | int id;
16 | @NotNull
17 | String world;
18 | @NotNull
19 | int x;
20 | @NotNull
21 | int y;
22 | @NotNull
23 | int z;
24 | @NotNull
25 | int account;
26 |
27 | public int getId() {
28 | return id;
29 | }
30 |
31 | public void setId(int id) {
32 | this.id = id;
33 | }
34 |
35 | public String getWorld() {
36 | return world;
37 | }
38 |
39 | public void setWorld(String world) {
40 | this.world = world;
41 | }
42 |
43 | public int getX() {
44 | return x;
45 | }
46 |
47 | public void setX(int x) {
48 | this.x = x;
49 | }
50 |
51 | public int getY() {
52 | return y;
53 | }
54 |
55 | public void setY(int y) {
56 | this.y = y;
57 | }
58 |
59 | public int getZ() {
60 | return z;
61 | }
62 |
63 | public void setZ(int z) {
64 | this.z = z;
65 | }
66 |
67 | public int getAccount() {
68 | return account;
69 | }
70 |
71 | public void setAccount(int account) {
72 | this.account = account;
73 | }
74 |
75 | @Override
76 | public String toString() {
77 | return "EBeanAccountChest(" + account + "," + world + ": " + x + "," + y + "," + z + ")";
78 | }
79 |
80 | }
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/commands/MoneyExecutor.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts.commands;
2 |
3 | import org.bukkit.command.Command;
4 | import org.bukkit.command.CommandSender;
5 | import org.bukkit.entity.Player;
6 |
7 | import static org.gestern.gringotts.Language.LANG;
8 |
9 | /**
10 | * Player commands.
11 | */
12 | public class MoneyExecutor extends GringottsAbstractExecutor {
13 |
14 | @Override
15 | public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args) {
16 | if (!(sender instanceof Player)) {
17 | sender.sendMessage(LANG.playerOnly);
18 | return false;
19 | }
20 | Player player = (Player) sender;
21 |
22 | if (args.length == 0) {
23 | // same as balance
24 | sendBalanceMessage(eco.player(player.getUniqueId()));
25 | return true;
26 | }
27 |
28 | String command = args[0];
29 |
30 | double value = 0;
31 | if (args.length == 2) {
32 | try {
33 | value = Double.parseDouble(args[1]);
34 | } catch (NumberFormatException ignored) {
35 | return false;
36 | }
37 |
38 | if ("withdraw".equals(command)) {
39 | withdraw(player, value);
40 |
41 | return true;
42 | } else if ("deposit".equals(command)) {
43 | deposit(player, value);
44 |
45 | return true;
46 | }
47 | } else if ( args.length == 3 && "pay".equals(command)) {
48 | try {
49 | value = Double.parseDouble(args[1]);
50 | } catch (NumberFormatException ignored) {
51 | return false;
52 | }
53 |
54 | // money pay
55 | return pay(player, value, args);
56 | }
57 | return false;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/accountholder/PlayerAccountHolder.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts.accountholder;
2 |
3 | import org.bukkit.OfflinePlayer;
4 |
5 | import java.util.UUID;
6 |
7 |
8 | public class PlayerAccountHolder implements AccountHolder {
9 |
10 | /**
11 | * Actual player owning the account.
12 | */
13 | public final OfflinePlayer accountHolder;
14 |
15 | public PlayerAccountHolder(OfflinePlayer player) {
16 | if (player != null)
17 | this.accountHolder = player;
18 | else throw new IllegalArgumentException("Attempted to create account holder with null player.");
19 | }
20 |
21 | @Override
22 | public String getName() {
23 | return accountHolder.getName();
24 | }
25 |
26 | @Override
27 | public void sendMessage(String message) {
28 | if (accountHolder.isOnline()) {
29 | accountHolder.getPlayer().sendMessage(message);
30 | }
31 | }
32 |
33 | /* (non-Javadoc)
34 | * @see java.lang.Object#hashCode()
35 | */
36 | @Override
37 | public int hashCode() {
38 | return getUUID().hashCode();
39 | }
40 |
41 | /* (non-Javadoc)
42 | * @see java.lang.Object#equals(java.lang.Object)
43 | */
44 | @Override
45 | public boolean equals(Object obj) {
46 | if (this == obj) {
47 | return true;
48 | }
49 |
50 | if (obj == null) {
51 | return false;
52 | }
53 |
54 | if (getClass() != obj.getClass()) {
55 | return false;
56 | }
57 |
58 | PlayerAccountHolder other = (PlayerAccountHolder) obj;
59 |
60 | return getUUID().equals(other.getUUID());
61 | }
62 |
63 | @Override
64 | public String getType() {
65 | return "player";
66 | }
67 |
68 | @Override
69 | public String toString() {
70 | return "PlayerAccountHolder(" + getName() + ")";
71 | }
72 |
73 | @Override
74 | public String getId() {
75 | return accountHolder.getUniqueId().toString();
76 | }
77 |
78 | public UUID getUUID() {
79 | return accountHolder.getUniqueId();
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/event/PlayerVaultListener.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts.event;
2 |
3 | import org.bukkit.entity.Player;
4 | import org.bukkit.event.EventHandler;
5 | import org.bukkit.event.Listener;
6 | import org.bukkit.event.block.SignChangeEvent;
7 | import org.gestern.gringotts.Gringotts;
8 | import org.gestern.gringotts.accountholder.AccountHolder;
9 | import org.gestern.gringotts.accountholder.PlayerAccountHolder;
10 |
11 | import static org.gestern.gringotts.Language.LANG;
12 | import static org.gestern.gringotts.Permissions.CREATEVAULT_ADMIN;
13 | import static org.gestern.gringotts.Permissions.CREATEVAULT_PLAYER;
14 | import static org.gestern.gringotts.event.VaultCreationEvent.*;
15 |
16 | /**
17 | * This Vault listener handles vault creation events for player vaults.
18 | *
19 | * @author jast
20 | */
21 | public class PlayerVaultListener implements Listener {
22 |
23 | @EventHandler
24 | public void vaultCreated(PlayerVaultCreationEvent event) {
25 | // some listener already claimed this event
26 | if (event.isValid()) {
27 | return;
28 | }
29 |
30 | // only interested in player vaults
31 | if (event.getType() != Type.PLAYER) {
32 | return;
33 | }
34 |
35 | SignChangeEvent cause = event.getCause();
36 | String ownername = cause.getLine(2);
37 |
38 | Player player = cause.getPlayer();
39 |
40 | if (!CREATEVAULT_PLAYER.allowed(player)) {
41 | player.sendMessage(LANG.vault_noVaultPerm);
42 |
43 | return;
44 | }
45 |
46 | AccountHolder owner;
47 | if (ownername != null && ownername.length() > 0 && CREATEVAULT_ADMIN.allowed(player)) {
48 | // attempting to create account for other player
49 | owner = Gringotts.getInstance().getAccountHolderFactory().get("player", ownername);
50 | if (owner == null) {
51 | return;
52 | }
53 | } else {
54 | // regular vault creation for self
55 | owner = new PlayerAccountHolder(player);
56 | }
57 |
58 | event.setOwner(owner);
59 | event.setValid(true);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/event/VaultCreator.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts.event;
2 |
3 | import org.bukkit.ChatColor;
4 | import org.bukkit.block.Sign;
5 | import org.bukkit.event.EventHandler;
6 | import org.bukkit.event.EventPriority;
7 | import org.bukkit.event.Listener;
8 | import org.bukkit.event.block.SignChangeEvent;
9 | import org.gestern.gringotts.AccountChest;
10 | import org.gestern.gringotts.Accounting;
11 | import org.gestern.gringotts.Gringotts;
12 | import org.gestern.gringotts.GringottsAccount;
13 | import org.gestern.gringotts.accountholder.AccountHolder;
14 |
15 | import static org.gestern.gringotts.Language.LANG;
16 |
17 | public class VaultCreator implements Listener {
18 |
19 | private final Accounting accounting = Gringotts.getInstance().getAccounting();
20 |
21 | /**
22 | * If the vault creation event was properly handled and an AccountHolder supplied, it will be created here.
23 | *
24 | * @param event event to handle
25 | */
26 | @EventHandler(priority = EventPriority.MONITOR)
27 | public void registerVault(PlayerVaultCreationEvent event) {
28 | // event has been marked invalid, ignore
29 | if (!event.isValid()) {
30 | return;
31 | }
32 |
33 | AccountHolder owner = event.getOwner();
34 | if (owner == null) {
35 | return;
36 | }
37 |
38 | GringottsAccount account = accounting.getAccount(owner);
39 |
40 | SignChangeEvent cause = event.getCause();
41 | // create account chest
42 | AccountChest accountChest = new AccountChest((Sign) cause.getBlock().getState(), account);
43 |
44 | // check for existence / add to tracking
45 | if (accounting.addChest(accountChest)) {
46 | // only embolden if the bold marker doesn't increase line length beyond 15
47 | if (cause.getLine(0).length() <= 13) {
48 | cause.setLine(0, ChatColor.BOLD + cause.getLine(0));
49 | }
50 |
51 | cause.setLine(2, owner.getName());
52 | cause.getPlayer().sendMessage(LANG.vault_created);
53 |
54 | } else {
55 | cause.setCancelled(true);
56 | cause.getPlayer().sendMessage(LANG.vault_error);
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/event/AccountListener.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts.event;
2 |
3 | import org.bukkit.Bukkit;
4 | import org.bukkit.block.BlockState;
5 | import org.bukkit.block.Sign;
6 | import org.bukkit.event.EventHandler;
7 | import org.bukkit.event.Listener;
8 | import org.bukkit.event.block.SignChangeEvent;
9 | import org.gestern.gringotts.Util;
10 |
11 | import java.util.regex.Matcher;
12 | import java.util.regex.Pattern;
13 |
14 | import static org.gestern.gringotts.Configuration.CONF;
15 | import static org.gestern.gringotts.event.VaultCreationEvent.*;
16 |
17 | /**
18 | * Listens for chest creation and destruction events.
19 | *
20 | * @author jast
21 | */
22 | public class AccountListener implements Listener {
23 |
24 | private final Pattern vaultPattern = Pattern.compile(
25 | CONF.vaultPattern,
26 | Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
27 |
28 | /**
29 | * Create an account chest by adding a sign marker over it.
30 | *
31 | * @param event Event data.
32 | */
33 | @EventHandler
34 | public void onSignChange(SignChangeEvent event) {
35 | final String line0 = event.getLine(0);
36 | final Matcher match = vaultPattern.matcher(line0);
37 |
38 | // consider only signs with proper formatting
39 | if (!match.matches()) {
40 | return;
41 | }
42 | final String typeStr = match.group(1).toUpperCase();
43 |
44 | Type type;
45 | // default vault is player
46 | if (typeStr.isEmpty()) {
47 | type = Type.PLAYER;
48 | } else {
49 | try {
50 | type = Type.valueOf(typeStr);
51 | } catch (IllegalArgumentException notFound) {
52 | return;
53 | }
54 | }
55 |
56 | // is sign attached to a valid vault container?
57 | BlockState signBlock = event.getBlock().getState();
58 | if (signBlock instanceof Sign && Util.chestBlock((Sign) signBlock) != null) {
59 | // we made it this far, throw the event to manage vault creation
60 | final VaultCreationEvent creation = new PlayerVaultCreationEvent(type, event);
61 |
62 | Bukkit.getServer().getPluginManager().callEvent(creation);
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/test/java/org/gestern/gringotts/UtilTest.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts;
2 |
3 | import org.junit.jupiter.api.Assertions;
4 | import org.junit.jupiter.api.Test;
5 |
6 | import static org.gestern.gringotts.Util.versionAtLeast;
7 |
8 | public class UtilTest {
9 |
10 | @Test
11 | public void versionAtLeast1() {
12 | Assertions.assertFalse(versionAtLeast("0", "1"));
13 | }
14 |
15 | @Test
16 | public void versionAtLeast2() {
17 | Assertions.assertFalse(versionAtLeast("0.1", "1"));
18 | }
19 |
20 | @Test
21 | public void versionAtLeast3() {
22 | Assertions.assertFalse(versionAtLeast("0.1", "1.2.1"));
23 | }
24 |
25 | @Test
26 | public void versionAtLeast4() {
27 | Assertions.assertFalse(versionAtLeast("0.0.0.0.1", "0.0.0.0.2"));
28 | }
29 |
30 | @Test
31 | public void versionAtLeast5() {
32 | Assertions.assertFalse(versionAtLeast("3.6", "4.6.1"));
33 | }
34 |
35 | @Test
36 | public void versionAtLeast6() {
37 | Assertions.assertTrue(versionAtLeast("3.6-b23", "3.6"));
38 | }
39 |
40 | @Test
41 | public void versionAtLeast6a() {
42 | Assertions.assertTrue(versionAtLeast("3.6", "3.6-b23"));
43 | }
44 |
45 | @Test
46 | public void versionAtLeast7() {
47 | Assertions.assertTrue(versionAtLeast("1.2.3", "1.2.3"));
48 | }
49 |
50 | @Test
51 | public void versionAtLeast8() {
52 | Assertions.assertTrue(versionAtLeast("one", "two"));
53 | }
54 |
55 | @Test
56 | public void versionAtLeast9() {
57 | Assertions.assertTrue(versionAtLeast("3.6.1", "2.6"));
58 | }
59 |
60 | @Test
61 | public void versionAtLeast10() {
62 | Assertions.assertTrue(versionAtLeast("3.6.1", "3.6"));
63 | }
64 |
65 | @Test
66 | public void versionAtLeast10a() {
67 | Assertions.assertFalse(versionAtLeast("3.6", "3.6.1"));
68 | }
69 |
70 | @Test
71 | public void versionAtLeast11() {
72 | Assertions.assertTrue(versionAtLeast("88.6", "3.6.99-b3"));
73 | }
74 |
75 | @Test
76 | public void versionAtLeast12() {
77 | Assertions.assertTrue(versionAtLeast("88.6", "3-b3"));
78 | }
79 |
80 | @Test
81 | public void versionAtLeast13() {
82 | Assertions.assertFalse(versionAtLeast("88.6", "99-b3"));
83 | }
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/api/impl/GringottsTransaction.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts.api.impl;
2 |
3 | import org.gestern.gringotts.api.Account;
4 | import org.gestern.gringotts.api.TaxedTransaction;
5 | import org.gestern.gringotts.api.Transaction;
6 | import org.gestern.gringotts.api.TransactionResult;
7 |
8 | import static org.gestern.gringotts.Configuration.CONF;
9 | import static org.gestern.gringotts.api.TransactionResult.ERROR;
10 | import static org.gestern.gringotts.api.TransactionResult.SUCCESS;
11 |
12 | public class GringottsTransaction implements Transaction {
13 |
14 | /**
15 | * Account from which this transaction will withdraw money.
16 | */
17 | protected final Account from;
18 |
19 | /**
20 | * Base value of this transaction.
21 | */
22 | protected final double value;
23 |
24 | /**
25 | * Create Transaction based on another (copy ctor).
26 | *
27 | * @param base transaction to copy
28 | */
29 | protected GringottsTransaction(GringottsTransaction base) {
30 | this.from = base.from;
31 | this.value = base.value;
32 | }
33 |
34 | /**
35 | * Create transaction with given source account and value.
36 | *
37 | * @param from Account from which this transaction will withdraw money
38 | * @param value base amount of this transaction
39 | */
40 | GringottsTransaction(Account from, double value) {
41 | this.from = from;
42 | this.value = value;
43 | }
44 |
45 | @Override
46 | public TransactionResult to(Account to) {
47 | if (value < 0) {
48 | return ERROR;
49 | }
50 |
51 | TransactionResult removed = from.remove(value);
52 |
53 | if (removed == SUCCESS) {
54 | TransactionResult added = to.add(value);
55 |
56 | if (added != SUCCESS) {
57 | // adding failed, refund source
58 | from.add(value);
59 | }
60 |
61 | // returns success or reason add failed
62 | return added;
63 | }
64 |
65 | // return reason remove failed
66 | return removed;
67 | }
68 |
69 | @Override
70 | public TaxedTransaction withTaxes() {
71 | double tax = CONF.transactionTaxFlat + value * CONF.transactionTaxRate;
72 | return new GringottsTaxedTransaction(this, tax);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/currency/Denomination.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts.currency;
2 |
3 | import org.bukkit.ChatColor;
4 |
5 | /**
6 | * Representation of a denomination within a currency.
7 | *
8 | * Note: this class has a natural ordering that is inconsistent with equals.
9 | * Specifically, the ordering is based purely on the value of the denomination, but not the type.
10 | */
11 | public class Denomination implements Comparable {
12 |
13 | private final DenominationKey key;
14 |
15 | /**
16 | * Value of one unit of this denomination in cents.
17 | */
18 | private final long value;
19 |
20 | /**
21 | * The name of a single unit of this denomination. The unit name is determined by explicit configuration,
22 | * configured displayName, or default item name (in this order).
23 | */
24 | private final String unitName;
25 |
26 | /**
27 | * The name for units of this denomination (plural). The unit name is determined by explicit configuration,
28 | * configured displayName, or default item name (in this order).
29 | */
30 | private final String unitNamePlural;
31 |
32 |
33 | public Denomination(DenominationKey key, long value, String unitName, String unitNamePlural) {
34 | this.key = key;
35 | this.value = value;
36 |
37 | this.unitName = unitName + ChatColor.RESET;
38 | this.unitNamePlural = unitNamePlural + ChatColor.RESET;
39 | }
40 |
41 | @Override
42 | public int compareTo(Denomination other) {
43 | // sort in descending value order
44 | return Long.compare(other.value, this.value); // Faster method.
45 | }
46 |
47 | @Override
48 | public String toString() {
49 | return String.format("{Denomination} %s : %d", key.type, value);
50 | }
51 |
52 | /**
53 | * Identification information for this denomination.
54 | */
55 | public DenominationKey getKey() {
56 | return key;
57 | }
58 |
59 | /**
60 | * Value of one unit of this denomination in cents.
61 | */
62 | public long getValue() {
63 | return value;
64 | }
65 |
66 | /**
67 | * The name of a single unit of this denomination. The unit name is determined by explicit configuration,
68 | * configured displayName, or default item name (in this order).
69 | */
70 | public String getUnitName() {
71 | return unitName;
72 | }
73 |
74 | /**
75 | * The name for units of this denomination (plural). The unit name is determined by explicit configuration,
76 | * configured displayName, or default item name (in this order).
77 | */
78 | public String getUnitNamePlural() {
79 | return unitNamePlural;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/main/resources/i18n/messages_de.yml:
--------------------------------------------------------------------------------
1 | playeronly: "Dieses Kommando kann nur von Spielern ausgeführt werden."
2 | noperm: "Du hast keine Berechtigung, Geld zu überweisen."
3 | balance: "Du hast zur Zeit insgesamt %balance"
4 | vault_balance: "In Tresoren: %balance"
5 | inv_balance: "Im Inventar: %balance"
6 | invalidaccount: "Ungültiger Spieler: %player"
7 | reload: "Gringotts: Einstellungen neu geladen!"
8 |
9 | pay:
10 | success:
11 | tax: "Transaktionssteuern erhoben: %value"
12 | sender: "%value an %player überwiesen."
13 | target: "%value von %player bekommen."
14 | insufficientFunds: "Du hast nicht genug Geld auf der Bank. Du hast: %balance. Du brauchst: %value"
15 | insufficientSpace:
16 | sender: "%player hat nicht genug Platz für %value"
17 | target: "%player hat versucht dir %value zu senden, aber du hast nicht genug Platz dafür."
18 | error: "Dein Versuch %player %value zu senden ist aus unbekannten Gründen gescheitert."
19 |
20 | deposit:
21 | success: "Du hast %value auf dein Konto überwiesen."
22 | error: "Es ist nicht möglich, %value auf dein Konto zu überweisen."
23 |
24 | withdraw:
25 | success: "%value von deinem Konto abgehoben."
26 | error: "Es ist nicht möglich, %value deinem Konto abzuheben."
27 |
28 | moneyadmin:
29 | b: "Kontostand von %player: %balance"
30 | add:
31 | sender: "%value auf %player's Konto eingezahlt."
32 | target: "Auf dein Konto wurde/n %value eingezahlt."
33 | error: "Es ist nicht möglich %value auf %player's Konto zu überweisen."
34 | rm:
35 | sender: "%value von %player's Konto entfernt."
36 | target: "Von deinem Konto wurde/n %value entfernt."
37 | error: "Es ist nicht möglich, %value von %player's Konto abzuheben."
38 |
39 | vault:
40 | created: "Ein Tresor wurde erstellt."
41 | error: "Fehler beim Erstellen eines Tresors."
42 | noVaultPerm: "Du hast keine Erlaubnis hier einen Tresor zu erstellen."
43 |
44 | plugins:
45 | towny:
46 | noTownVaultPerm: "Du kannst hier keinen Town-Tresor erstellen."
47 | noTownResident: "Du kannst keine Town-Tresor erstellen, weil du nicht Bürger einer Town bist."
48 | noNationVaultPerm: "Du kannst hier keine Nation-Tresore erstellen."
49 | notInNation: "Du kannst keine Nation-Tresore erstellen, weil du nicht Bürger einer Nation bist"
50 | vault:
51 | insufficientFunds: "Nicht genug Geld."
52 | insufficientSpace: "Nicht genug Platz."
53 | unknownError: "Unbekannter Fehler."
54 | notImplemented: "Gringotts unterstützt keine Banken."
55 | faction:
56 | noFactionVaultPerm: "Du kannst hier keinen Faction-Tresor erstellen."
57 | notInFaction: "Du kannst keinen Faction-Tresor erstellen, weil du nicht in einer Faction bist."
58 |
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/data/DAO.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts.data;
2 |
3 | import org.gestern.gringotts.AccountChest;
4 | import org.gestern.gringotts.GringottsAccount;
5 | import org.gestern.gringotts.GringottsStorageException;
6 | import org.gestern.gringotts.accountholder.AccountHolder;
7 |
8 | import java.util.List;
9 |
10 | public interface DAO {
11 | /**
12 | * Save an account chest to database.
13 | *
14 | * @param chest chest to save
15 | * @return true if chest was stored, false otherwise
16 | * @throws GringottsStorageException when storage failed
17 | */
18 | boolean storeAccountChest(AccountChest chest);
19 |
20 | /**
21 | * Remove an account chest from the datastore.
22 | *
23 | * @param chest chest to remove
24 | * @return true if the chest was deleted, false if no chest was deleted.
25 | */
26 | boolean destroyAccountChest(AccountChest chest);
27 |
28 | /**
29 | * Store the given Account to DB.
30 | *
31 | * @param account account to store
32 | * @return true if an account was stored, false if it already existed
33 | */
34 | boolean storeAccount(GringottsAccount account);
35 |
36 | /**
37 | * Return whether a given account owner has an account.
38 | *
39 | * @param accountHolder account holder to check
40 | * @return true if the account holder has an account associated with them
41 | */
42 | boolean hasAccount(AccountHolder accountHolder);
43 |
44 | /**
45 | * Get set of all chests registered with Gringotts.
46 | * If a stored chest turns out to be invalid, that chest may be removed from storage.
47 | *
48 | * @return set of all chests registered with Gringotts
49 | */
50 | List getChests();
51 |
52 | /**
53 | * Get all chests belonging to the given account.
54 | * If a stored chest turns out to be invalid, that chest is removed from storage.
55 | *
56 | * @param account account to fetch chests for.
57 | * @return account to get chests for
58 | */
59 | List getChests(GringottsAccount account);
60 |
61 | /**
62 | * Store an amount of cents to a given account.
63 | *
64 | * @param account account to store amount to
65 | * @param amount amount to store to account
66 | * @return true if storing was successful, false otherwise.
67 | */
68 | boolean storeCents(GringottsAccount account, long amount);
69 |
70 | /**
71 | * Get the cents stored for a given account.
72 | *
73 | * @param account account to query
74 | * @return amount of cents stored in the account, 0 if the account is not stored
75 | */
76 | long getCents(GringottsAccount account);
77 |
78 | /**
79 | * Delete an account and associated data from the storage.
80 | *
81 | * @param acc account to delete
82 | */
83 | void deleteAccount(GringottsAccount acc);
84 |
85 | /**
86 | * Shutdown the database connection.
87 | */
88 | void shutdown();
89 | }
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/Accounting.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts;
2 |
3 | import org.gestern.gringotts.accountholder.AccountHolder;
4 |
5 | import java.util.List;
6 | import java.util.logging.Logger;
7 |
8 | import static org.gestern.gringotts.Gringotts.getInstance;
9 |
10 | /**
11 | * Manages accounts.
12 | *
13 | * @author jast
14 | */
15 | public class Accounting {
16 |
17 | private final Logger log = getInstance().getLogger();
18 |
19 | /**
20 | * Get the account associated with an account holder.
21 | * If it was not yet stored in the data storage, it will be persisted.
22 | *
23 | * @param owner account holder
24 | * @return account associated with an account holder
25 | */
26 | public GringottsAccount getAccount(AccountHolder owner) {
27 | GringottsAccount account = new GringottsAccount(owner);
28 | if (!getInstance().getDao().hasAccount(owner)) // TODO can we do this via idempotent store action instead?
29 | {
30 | getInstance().getDao().storeAccount(account);
31 | }
32 |
33 | return account;
34 | }
35 |
36 | /**
37 | * Determine if a given AccountChest would be connected to an AccountChest already in storage.
38 | * Alas! need to call this every time we try to add an account chest, since chests can be added
39 | * without us noticing ...
40 | *
41 | * @param chest chest to check for connectedness
42 | * @param allChests set of all chests that might be a candidate for connectedness
43 | * @return whether given chest is connected to any existing chest
44 | */
45 | // TODO perhaps this could be more elegantly done with block metadata
46 | private boolean chestConnected(AccountChest chest, List allChests) {
47 | for (AccountChest ac : allChests) {
48 | if (ac.connected(chest)) {
49 | return true;
50 | }
51 | }
52 | return false;
53 | }
54 |
55 | /**
56 | * Save an AccountChest to Account association.
57 | *
58 | * @param chest chest to add to the account
59 | * @return false if the specified AccountChest is already registered or would be connected to
60 | * a registered chest. true if the association was successful.
61 | * @throws GringottsStorageException when saving of account chest failed
62 | */
63 | public boolean addChest(AccountChest chest) {
64 |
65 | // TODO refactor to do a more intelligent/quick query
66 | List allChests = getInstance().getDao().getChests();
67 |
68 | // if there is an invalid stored chest on location of new chest, remove it from storage.
69 | if (allChests.contains(chest)) {
70 | getInstance().getLogger().info("removing orphaned vault: " + chest);
71 | getInstance().getDao().destroyAccountChest(chest);
72 | allChests.remove(chest);
73 | }
74 |
75 | if (chestConnected(chest, allChests)) {
76 | return false;
77 | }
78 |
79 | if (!getInstance().getDao().storeAccountChest(chest)) {
80 | throw new GringottsStorageException("Could not save account chest: " + chest);
81 | }
82 |
83 | return true;
84 | }
85 |
86 | }
87 |
--------------------------------------------------------------------------------
/plugin.yml:
--------------------------------------------------------------------------------
1 | name: Gringotts
2 | authors: [jasticE, squidicuz, eXeC64, nikosgram13]
3 | website: https://github.com/MinecraftWars/Gringotts
4 |
5 | version: ${version}
6 | main: org.gestern.gringotts.Gringotts
7 | load: STARTUP
8 | database: true
9 |
10 | api-version: 1.13
11 |
12 | awareness:
13 | - !@UTF8
14 |
15 | depend: []
16 | softdepend: []
17 | loadbefore: [Vault]
18 |
19 | commands:
20 | balance:
21 | description: Shows your vault balance
22 | usage: /balance
23 | aliases: money
24 | money:
25 | aliases: [money, m]
26 | description: Money actions
27 | usage: |
28 | /money
29 | /money pay
30 | moneyadmin:
31 | aliases: [moneyadm, mad]
32 | description: Gringotts admin actions
33 | usage: |
34 | /moneyadm add
35 | /moneyadm rm
36 | /moneyadm b
37 | permission: gringotts.admin
38 | gringotts:
39 | aliases: [grin]
40 | usage: |
41 | /gringotts reload
42 | permission: gringotts.admin
43 |
44 | permissions:
45 | gringotts.createvault:
46 | description: Create vaults
47 | default: true
48 | children:
49 | gringotts.createvault.player: true
50 | gringotts.createvault.faction: true
51 | gringotts.createvault.town: true
52 | gringotts.createvault.nation: true
53 |
54 | gringotts.createvault.admin:
55 | description: Create vault for other players/accounts
56 | default: op
57 | gringotts.createvault.player:
58 | description: Create player vaults
59 | default: true
60 | gringotts.createvault.faction:
61 | description: Create faction vaults (Factions only)
62 | default: true
63 | gringotts.createvault.town:
64 | description: Create town vaults (Towny only)
65 | default: true
66 | gringotts.createvault.nation:
67 | description: Create nation vaults (Towny only)
68 | default: true
69 | gringotts.createvault.worldguard:
70 | description: Create region vaults (WorldGuard only)
71 | default: true
72 |
73 | gringotts.usevault:
74 | description: Use any type of vault
75 | default: true
76 | children:
77 | gringotts.usevault.inventory: true
78 | gringotts.usevault.enderchest: true
79 | gringotts.usevault.inventory:
80 | description: Use player's inventory as 'vault'
81 | default: true
82 | gringotts.usevault.enderchest:
83 | description: Use player's enderchest inventory as vault
84 | default: true
85 |
86 | gringotts.command:
87 | description: Allow all user transaction commands
88 | default: true
89 | children:
90 | gringotts.transfer: true
91 | gringotts.command.withdraw: true
92 | gringotts.command.deposit: true
93 | gringotts.transfer:
94 | description: Allow money transfer commands
95 | default: true
96 | gringotts.command.withdraw:
97 | description: Allow withdrawal of money from chest storage to inventory.
98 | default: true
99 | gringotts.command.deposit:
100 | description: Allow deposit of money to chest storage from inventory.
101 | default: true
102 |
103 | gringotts.admin:
104 | description: Use all /moneyadmin commands
105 | default: op
106 |
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/event/VaultCreationEvent.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts.event;
2 |
3 | import org.bukkit.event.Event;
4 | import org.bukkit.event.HandlerList;
5 | import org.gestern.gringotts.Language;
6 | import org.gestern.gringotts.accountholder.AccountHolder;
7 |
8 | /**
9 | * Event that is thrown after Gringotts detects a vault creation.
10 | * When thrown, it includes the type of the vault, for example "player" or "faction"
11 | * and is set to invalid with an empty message.
12 | *
13 | * Listeners may set the event to valid, which will cause a vault of the given type to be
14 | * created by Gringotts. Optionally, a custom message will be sent to the owner of the account.
15 | *
16 | * @author jast
17 | */
18 | public class VaultCreationEvent extends Event {
19 |
20 | protected static final HandlerList handlers = new HandlerList();
21 |
22 | private final Type type;
23 | private boolean isValid = false;
24 | private AccountHolder owner;
25 |
26 | /**
27 | * Create VaultCreationEvent for a given vault type.
28 | *
29 | * @param type Type of vault being created
30 | */
31 | public VaultCreationEvent(Type type) {
32 | this.type = type;
33 | }
34 |
35 | public enum Type {
36 | PLAYER,
37 | FACTION,
38 | TOWN,
39 | NATION,
40 | REGION
41 |
42 | }
43 |
44 | @SuppressWarnings("unused")
45 | public static HandlerList getHandlerList() {
46 | return handlers;
47 | }
48 |
49 | public Type getType() {
50 | return type;
51 | }
52 |
53 | /**
54 | * Return whether this is a valid vault type.
55 | * false by default. A Listener may set this to true.
56 | *
57 | * @return whether this is a valid vault.
58 | */
59 | public boolean isValid() {
60 | return isValid;
61 | }
62 |
63 | /**
64 | * Set valid status of vault being created. This is false by default.
65 | * A listener that sets this to true must also ensure that an owner is supplied.
66 | *
67 | * @param valid valid status to set
68 | */
69 | public void setValid(boolean valid) {
70 | this.isValid = valid;
71 | }
72 |
73 | /**
74 | * Get message sent to account owner on creation of this vault.
75 | *
76 | * @return message sent to account owner on creation of this vault.
77 | */
78 | public String getMessage() {
79 | return Language.LANG.vault_created;
80 | }
81 |
82 | /**
83 | * Get account holder supplied as owner for the vault being created.
84 | *
85 | * @return account holder supplied as owner for the vault being created.
86 | */
87 | public AccountHolder getOwner() {
88 | return owner;
89 | }
90 |
91 | /**
92 | * Set the account holder acting as the owner of the vault being created.
93 | * When the valid status is also true, this will enable the vault to be registered with Gringotts.
94 | *
95 | * @param owner owner of the vault being created
96 | */
97 | public void setOwner(AccountHolder owner) {
98 | this.owner = owner;
99 | }
100 |
101 | @Override
102 | public HandlerList getHandlers() {
103 | return handlers;
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/messages.yml:
--------------------------------------------------------------------------------
1 | # Here you can change the output of all messages sent to your players via chat.
2 | # To do so you just have to rewrite the values below. Just be sure to not put any %-variables in the message that were not there to begin with,
3 | # it will just be displayed as %whateveryouwrote in the message sent.
4 | #
5 | # %value stands for an amount of money
6 | # %player means either the player executing the command, the player you interact with or the player whose account you want to manipulate
7 | # %balance stands for the amount of money a player has
8 | #
9 | # sender stands for the message the one executing the command gets, target for the other person who is affected.
10 | # Errors and other messages are only displayed for the one executing the command
11 |
12 |
13 | playeronly: "This command can only be run by a player."
14 | noperm: "You do not have permission to transfer money."
15 | balance: "Your current balance: %balance"
16 | vault_balance: "Vault balance: %balance"
17 | inv_balance: "Inventory balance: %balance"
18 | invalidaccount: "Invalid account: %player"
19 | reload: "Gringotts: Reloaded configuration!"
20 |
21 | pay:
22 | success:
23 | tax: "Transaction tax deducted from your account: %value"
24 | sender: "Sent %value to %player. "
25 | target: "Received %value from %player."
26 | insufficientFunds: "Your account has insufficient balance. Current balance: %balance. Required: %value"
27 | insufficientSpace:
28 | sender: "%player has insufficient storage space for %value"
29 | target: "%player tried to send %value, but you don't have enough space for that amount."
30 | error: "Your attempt to send %value to %player failed for unknown reasons."
31 |
32 | deposit:
33 | success: "Deposited %value to your storage."
34 | error: "Unable to deposit %value to your storage."
35 |
36 | withdraw:
37 | success: "Withdrew %value from your storage."
38 | error: "Unable to withdraw %value from your storage."
39 |
40 | moneyadmin:
41 | b: "Balance of account %player: %balance"
42 | add:
43 | sender: "Added %value to account %player"
44 | target: "Added to your account: %value"
45 | error: "Could not add %value to account %player"
46 | rm:
47 | sender: "Removed %value from account %player"
48 | target: "Removed from your account: %value"
49 | error: "Could not remove %value from account %player"
50 |
51 | vault:
52 | created: "Created vault successfully."
53 | error: "Failed to create vault."
54 | noVaultPerm: "You do not have permission to create vaults here."
55 |
56 | plugins:
57 | towny:
58 | noTownVaultPerm: "You do not have permission to create town vaults here."
59 | noTownResident: "Cannot create town vault: You are not resident of a town."
60 | noNationVaultPerm: "You do not have permission to create nation vaults here."
61 | notInNation: "Cannot create nation vault: You do not belong to a nation."
62 | vault:
63 | insufficientFunds: "Insufficient funds."
64 | insufficientSpace: "Insufficient space."
65 | unknownError: "Unknown failure."
66 | notImplemented: "Gringotts does not support banks"
67 | faction:
68 | noFactionVaultPerm: "You do not have permission to create a faction vault here."
69 | notInFaction: "Cannot create faction vault: You are not in a faction."
70 |
--------------------------------------------------------------------------------
/doc/usage.md:
--------------------------------------------------------------------------------
1 | Usage
2 | =====
3 |
4 | Storing money in an account requires a Gringotts vault. A vault consists of a container, which can be either chest, dispenser or furnace, and a sign above or on it declaring it as a vault. A player or faction may claim any number of vaults. Vaults are not protected from access through other players. If you would like them to be, you may use additional plugins such as [LWC](http://dev.bukkit.org/server-mods/lwc/) or [WorldGuard](http://dev.bukkit.org/server-mods/worldguard/).
5 |
6 | Vaults
7 | ------
8 |
9 | Gringotts supports vaults for players as well as other plugins: currently Factions, Towny and WorldGuard. All vaults are created by placing a sign onto or above a container and writing the vault type on first line the sign. When a vault is successfully created, you will receive a message.
10 |
11 | ### Player vaults ###
12 |
13 | First line: `[vault]`
14 | Third line: will display your name on successful creation.
15 |
16 | ### Faction vaults ###
17 |
18 | First line: `[faction vault]`
19 | Third line: will display your faction's tag on successful creation.
20 |
21 | ### Towny vaults ###
22 |
23 | * Town vaults first line: `[town vault]`
24 | * Nation vaults first line: `[nation vault]`
25 |
26 | If it was created correctly, the sign will display your town's or nation's tag on the third line.
27 |
28 | ### WorldGuard vaults ###
29 |
30 | First line: `[region vault]`
31 | Third line: region's id
32 |
33 | Writing the region id manually is required because a player may be part of several regions. If the vault has been created correctly, you will receive a message that the vault has been created.
34 |
35 | ### Creating vaults for other players/accounts ###
36 |
37 | The permission `createvault.forothers` allows you to create vaults for other players or factions, towns, etc. that do not belong to you. To create a vault for others, write the appropriate vault type on the first line and the designated owner on the third line (player, faction tag, town name).
38 |
39 | Commands
40 | --------
41 |
42 | Arguments between `<>` (ex ``) are mandatory while ones between `[]` (ex. `[type]`) are optionals.
43 |
44 | ### User commands ###
45 |
46 | | Command | Description | Aliases |
47 | | ------------------------------ | --------------------------------------- | --------------- |
48 | | `/money` | Display your account's current balance. | `/m`, `/balance`|
49 | | `/money pay ` | Pay an amount to a player. The transaction will only succeed if your account has at least the given amount plus any taxes that apply, and the receiving account has enough capacity for the amount. | none |
50 | | `/money withdraw ` | Withdraw an amount from chest storage into inventory.| none |
51 | | `/money deposit ` | Deposit an amount from inventory into chest storage. | none |
52 |
53 | ### Admin commands ###
54 |
55 | | Command | Description | Aliases |
56 | | ------------------------------------------- | ---------------------------------------------------------------------------- | ------- |
57 | | `/moneyadmin b ` | Get the balance of a player's account. | none |
58 | | `/moneyadmin add [type]` | Add an amount of money to a player's account. | none |
59 | | `/moneyadmin rm [type]` | Remove an amount of money from a player's account. | none |
60 | | `/gringotts reload` | Reload Gringotts config.yml and messages.yml and apply any changed settings. | none |
61 |
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/accountholder/AccountHolderFactory.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts.accountholder;
2 |
3 | import org.bukkit.Bukkit;
4 | import org.bukkit.OfflinePlayer;
5 |
6 | import java.util.LinkedHashMap;
7 | import java.util.Map;
8 | import java.util.UUID;
9 |
10 | /**
11 | * Manages creating various types of AccountHolder centrally.
12 | *
13 | * @author jast
14 | */
15 | public class AccountHolderFactory {
16 |
17 | private final Map accountHolderProviders = new LinkedHashMap<>();
18 |
19 | public AccountHolderFactory() {
20 | // linked HashMap maintains iteration order -> prefer player to be checked first
21 | accountHolderProviders.put("player", new PlayerAccountHolderProvider());
22 |
23 | // TODO support banks
24 | // TODO support virtual accounts
25 | }
26 |
27 | /**
28 | * Get an account holder with automatically determined type, based on the owner's id.
29 | *
30 | * @param owner name of the account holder
31 | * @return account holder for the given owner name, or null if none could be determined
32 | */
33 | public AccountHolder get(String owner) {
34 | for (AccountHolderProvider provider : accountHolderProviders.values()) {
35 | AccountHolder accountHolder = provider.getAccountHolder(owner);
36 |
37 | if (accountHolder != null) {
38 | return accountHolder;
39 | }
40 | }
41 |
42 | return null;
43 | }
44 |
45 | /**
46 | * Get an account holder of known type.
47 | *
48 | * @param type type of the account
49 | * @param owner name of the account holder
50 | * @return account holder of given type with given owner name, or null if none could be determined or type is not
51 | * supported.
52 | */
53 | public AccountHolder get(String type, String owner) {
54 | AccountHolderProvider provider = accountHolderProviders.get(type);
55 | AccountHolder accountHolder = null;
56 |
57 | if (provider != null) {
58 | accountHolder = provider.getAccountHolder(owner);
59 | }
60 |
61 | return accountHolder;
62 | }
63 |
64 | public void registerAccountHolderProvider(String type, AccountHolderProvider provider) {
65 | accountHolderProviders.put(type, provider);
66 | }
67 |
68 | private static class PlayerAccountHolderProvider implements AccountHolderProvider {
69 |
70 | @Override
71 | public AccountHolder getAccountHolder(String uuidOrName) {
72 | if (uuidOrName == null) {
73 | return null;
74 | }
75 |
76 | OfflinePlayer player;
77 |
78 | try {
79 | UUID playerId = UUID.fromString(uuidOrName);
80 | player = Bukkit.getOfflinePlayer(playerId);
81 | } catch (IllegalArgumentException ignored) {
82 | // don't use getOfflinePlayer(String) because that will do a blocking web request
83 | // rather iterate this array, should be quick enough
84 | for (OfflinePlayer p : Bukkit.getOfflinePlayers()) {
85 | if (uuidOrName.equals(p.getName())) return new PlayerAccountHolder(p);
86 | }
87 |
88 | return null;
89 | }
90 |
91 | // if this player has ever played on the server, they are a legit account holder
92 | if (player.isOnline() || player.hasPlayedBefore()) {
93 | return new PlayerAccountHolder(player);
94 | } else {
95 | return null;
96 | }
97 | }
98 |
99 | }
100 |
101 | }
102 |
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/dependency/Dependency.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts.dependency;
2 |
3 | import org.bukkit.Bukkit;
4 | import org.bukkit.plugin.Plugin;
5 | import org.bukkit.plugin.PluginDescriptionFile;
6 | import org.gestern.gringotts.Gringotts;
7 |
8 | import java.util.logging.Logger;
9 |
10 | import static org.gestern.gringotts.Util.versionAtLeast;
11 |
12 | /**
13 | * Manages plugin dependencies.
14 | *
15 | * @author jast
16 | */
17 | public enum Dependency {
18 |
19 | /**
20 | * Singleton dependency manager instance.
21 | */
22 | DEP;
23 |
24 | public final FactionsHandler factions;
25 | public final TownyHandler towny;
26 | public final DependencyHandler vault;
27 | public final WorldGuardHandler worldguard;
28 | private final Logger log = Gringotts.getInstance().getLogger();
29 |
30 | /**
31 | * Initialize plugin dependencies. The plugins themselves do not need to be loaded before this is called,
32 | * but the classes must be visible to the classloader.
33 | */
34 | Dependency() {
35 | factions = FactionsHandler.getFactionsHandler(hookPlugin(
36 | "Factions",
37 | "com.massivecraft.factions.TerritoryAccess",
38 | "2.12.0"));
39 | towny = TownyHandler.getTownyHandler(hookPlugin(
40 | "Towny",
41 | "com.palmergames.bukkit.towny.Towny",
42 | "0.89.0.0"));
43 | vault = new GenericHandler(hookPlugin(
44 | "Vault",
45 | "net.milkbowl.vault.Vault",
46 | "1.5.0"));
47 | worldguard = WorldGuardHandler.getWorldGuardHandler(hookPlugin(
48 | "WorldGuard",
49 | "com.sk89q.worldguard.bukkit.WorldGuardPlugin",
50 | "6.2"));
51 | }
52 |
53 | /**
54 | * Determines if all packages in a String array are within the Classpath
55 | * This is the best way to determine if a specific plugin exists and will be
56 | * loaded. If the plugin package isn't loaded, we shouldn't bother waiting
57 | * for it!
58 | *
59 | * @param packages String Array of package names to check
60 | * @return Success or Failure
61 | */
62 | private static boolean packagesExists(String... packages) {
63 | try {
64 | for (String pkg : packages) {
65 | Class.forName(pkg);
66 | }
67 |
68 | return true;
69 | } catch (Exception e) {
70 | return false;
71 | }
72 | }
73 |
74 | /**
75 | * Attempt to hook a plugin dependency.
76 | *
77 | * @param name Name of the plugin.
78 | * @param classpath classpath to check for
79 | * @param minVersion minimum version of the plugin. The plugin will still be hooked if this version is not
80 | * satisfied,
81 | * but a warning will be emitted.
82 | * @return the plugin object when hooked successfully, or null if not.
83 | */
84 | private Plugin hookPlugin(String name, String classpath, String minVersion) {
85 | Plugin plugin;
86 |
87 | if (packagesExists(classpath)) {
88 | plugin = Bukkit.getServer().getPluginManager().getPlugin(name);
89 |
90 | log.info("Plugin " + name + " hooked.");
91 |
92 | PluginDescriptionFile desc = plugin.getDescription();
93 | String version = desc.getVersion();
94 |
95 | if (!versionAtLeast(version, minVersion)) {
96 | log.warning("Plugin dependency " + name + " is version " + version +
97 | ". Expected at least " + minVersion + " -- Errors may occur.");
98 |
99 | }
100 | } else {
101 | log.warning("Unable to hook plugin " + name);
102 | plugin = null;
103 | }
104 |
105 | return plugin;
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/api/Eco.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts.api;
2 |
3 | import java.util.Set;
4 | import java.util.UUID;
5 |
6 | public interface Eco {
7 |
8 | /**
9 | * Access a generic account for the given id. The type of the account is defined by the economy plugin.
10 | * The recommended behavior is to return a player account if a player of the name exists, and return any other
11 | * type of account
12 | * if no player of that name is known to the economy.
13 | *
14 | * @param id id of the account
15 | * @return The account representation.
16 | */
17 | Account account(String id);
18 |
19 | /**
20 | * Access a player account for the player with given name. If possible, it is recommended to use the player(UUID)
21 | * method instead of this one.
22 | * If a player can have multiple accounts, the account returned is defined by the economy plugin.
23 | * For example, if the economy supports one account per world,
24 | * return the account associated with the world the player is currently in.
25 | * The representation of the money of a player account may be in-game items or virtual.
26 | *
27 | * @param name The name of the player owning the account.
28 | * @return The player account representation
29 | */
30 | PlayerAccount player(String name);
31 |
32 | /**
33 | * Access a player account for the player with given uuid. If a player can have multiple accounts,
34 | * the account returned is defined by the economy plugin. For example, if the economy supports one account per
35 | * world,
36 | * return the account associated with the world the player is currently in.
37 | * The representation of the money of a player account may be in-game items or virtual.
38 | *
39 | * @param id The unique id of the player owning the account.
40 | * @return The player account representation
41 | */
42 | PlayerAccount player(UUID id);
43 |
44 | /**
45 | * Access a bank account with given name.
46 | * The representation of the money in a bank account may be in-game items or virtual.
47 | * Support for this method is optional.
48 | *
49 | * @param name The name or id of the bank.
50 | * @return The bank account representation.
51 | */
52 | BankAccount bank(String name);
53 |
54 | /**
55 | * Access custom account type. Implementors must support this method, but may choose to implement via another
56 | * account type internally.
57 | *
58 | * @param type type of account
59 | * @param id account id
60 | * @return an account of the given type and id
61 | */
62 | Account custom(String type, String id);
63 |
64 | /**
65 | * Access a Factions faction account with the given id.
66 | *
67 | * @param id faction id
68 | * @return the faction's account
69 | */
70 | Account faction(String id);
71 |
72 | /**
73 | * Access a Towny town account with the given id.
74 | *
75 | * @param id/name of a Towny town
76 | * @return account for a Towny town
77 | */
78 | Account town(String id);
79 |
80 | /**
81 | * Access a Towny nation account with the given id.
82 | *
83 | * @param id id/name of a Towny nation
84 | * @return account for a Towny nation with the given id.
85 | */
86 | Account nation(String id);
87 |
88 | /**
89 | * The currency for this Economy.
90 | *
91 | * @return The currency for this Economy.
92 | */
93 | Currency currency();
94 |
95 | /**
96 | * Return whether this economy supports banks.
97 | *
98 | * @return true if this economy has bank support, false otherwise.
99 | */
100 | boolean supportsBanks();
101 |
102 | /**
103 | * Get a set of all banks currently registered by the economy.
104 | *
105 | * @return a set of the names of all banks currently registered by the economy.
106 | */
107 | Set getBanks();
108 | }
109 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.gitignore.io/api/java,maven,intellij,intellij+all,intellij+iml
3 |
4 | ### Intellij ###
5 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
6 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
7 |
8 | # User-specific stuff:
9 | .idea/**/workspace.xml
10 | .idea/**/tasks.xml
11 | .idea/dictionaries
12 |
13 | # Sensitive or high-churn files:
14 | .idea/**/dataSources/
15 | .idea/**/dataSources.ids
16 | .idea/**/dataSources.xml
17 | .idea/**/dataSources.local.xml
18 | .idea/**/sqlDataSources.xml
19 | .idea/**/dynamic.xml
20 | .idea/**/uiDesigner.xml
21 |
22 | # Gradle:
23 | .idea/**/gradle.xml
24 | .idea/**/libraries
25 |
26 | # CMake
27 | cmake-build-debug/
28 |
29 | # Mongo Explorer plugin:
30 | .idea/**/mongoSettings.xml
31 |
32 | ## File-based project format:
33 | *.iws
34 |
35 | ## Plugin-specific files:
36 |
37 | # IntelliJ
38 | /out/
39 |
40 | # mpeltonen/sbt-idea plugin
41 | .idea_modules/
42 |
43 | # JIRA plugin
44 | atlassian-ide-plugin.xml
45 |
46 | # Cursive Clojure plugin
47 | .idea/replstate.xml
48 |
49 | # Ruby plugin and RubyMine
50 | /.rakeTasks
51 |
52 | # Crashlytics plugin (for Android Studio and IntelliJ)
53 | com_crashlytics_export_strings.xml
54 | crashlytics.properties
55 | crashlytics-build.properties
56 | fabric.properties
57 |
58 | ### Intellij Patch ###
59 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
60 |
61 | # *.iml
62 | # modules.xml
63 | # .idea/misc.xml
64 | # *.ipr
65 |
66 | # Sonarlint plugin
67 | .idea/sonarlint
68 |
69 | ### Intellij+all ###
70 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
71 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
72 |
73 | # User-specific stuff:
74 |
75 | # Sensitive or high-churn files:
76 |
77 | # Gradle:
78 |
79 | # CMake
80 |
81 | # Mongo Explorer plugin:
82 |
83 | ## File-based project format:
84 |
85 | ## Plugin-specific files:
86 |
87 | # IntelliJ
88 |
89 | # mpeltonen/sbt-idea plugin
90 |
91 | # JIRA plugin
92 |
93 | # Cursive Clojure plugin
94 |
95 | # Ruby plugin and RubyMine
96 |
97 | # Crashlytics plugin (for Android Studio and IntelliJ)
98 |
99 | ### Intellij+all Patch ###
100 | # Ignores the whole idea folder
101 | # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
102 |
103 | .idea/
104 |
105 | ### Intellij+iml ###
106 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
107 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
108 |
109 | # User-specific stuff:
110 |
111 | # Sensitive or high-churn files:
112 |
113 | # Gradle:
114 |
115 | # CMake
116 |
117 | # Mongo Explorer plugin:
118 |
119 | ## File-based project format:
120 |
121 | ## Plugin-specific files:
122 |
123 | # IntelliJ
124 |
125 | # mpeltonen/sbt-idea plugin
126 |
127 | # JIRA plugin
128 |
129 | # Cursive Clojure plugin
130 |
131 | # Ruby plugin and RubyMine
132 |
133 | # Crashlytics plugin (for Android Studio and IntelliJ)
134 |
135 | ### Intellij+iml Patch ###
136 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
137 |
138 | *.iml
139 | modules.xml
140 | .idea/misc.xml
141 | *.ipr
142 |
143 | ### Java ###
144 | # Compiled class file
145 | *.class
146 |
147 | # Log file
148 | *.log
149 |
150 | # BlueJ files
151 | *.ctxt
152 |
153 | # Mobile Tools for Java (J2ME)
154 | .mtj.tmp/
155 |
156 | # Package Files #
157 | *.jar
158 | *.war
159 | *.ear
160 | *.zip
161 | *.tar.gz
162 | *.rar
163 |
164 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
165 | hs_err_pid*
166 |
167 | ### Maven ###
168 | target/
169 | pom.xml.tag
170 | pom.xml.releaseBackup
171 | pom.xml.versionsBackup
172 | pom.xml.next
173 | release.properties
174 | dependency-reduced-pom.xml
175 | buildNumber.properties
176 | .mvn/timing.properties
177 |
178 | # Avoid ignoring Maven wrapper jar file (.jar files are usually ignored)
179 | !/.mvn/wrapper/maven-wrapper.jar
180 |
181 |
182 | # End of https://www.gitignore.io/api/java,maven,intellij,intellij+all,intellij+iml
183 | .DS_Store
184 |
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/api/Account.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts.api;
2 |
3 | /**
4 | * Defines actions possible on an account in an economy.
5 | */
6 | public interface Account {
7 |
8 | /**
9 | * Check if this account exists in the economy system.
10 | *
11 | * @return true if this account exists, false otherwise.
12 | */
13 | boolean exists();
14 |
15 | /**
16 | * Create this account, if it does not exist, and creation is possible.
17 | * If creation was successful, the Account object returned as result of this method must return true to exists().
18 | *
19 | * @return Representation of this account after it has been created.
20 | */
21 | Account create();
22 |
23 | /**
24 | * Delete this account from the economy system, if it exists, and account deletion is possible.
25 | * If deletion was successful, the Account object returned as result of this method must return false to exists().
26 | *
27 | * @return Representation of this account after it has been deleted.
28 | */
29 | Account delete();
30 |
31 | /**
32 | * Return the balance of this account.
33 | *
34 | * @return the balance of this account.
35 | */
36 | double balance();
37 |
38 | /**
39 | * Return the vault balance of this account.
40 | *
41 | * @return the vault balance of this account.
42 | */
43 | double vaultBalance();
44 |
45 | /**
46 | * Return the inventory balance of this account.
47 | *
48 | * @return the inventory balance of this account.
49 | */
50 | double invBalance();
51 |
52 | /**
53 | * Return whether this account has at least the specified amount.
54 | *
55 | * @param value amount to check
56 | * @return whether this account has at least the specified amount.
57 | */
58 | boolean has(double value);
59 |
60 | /**
61 | * Set the balance of this account.
62 | * Note: it is preferred to use the add and remove methods for any transactions that actually have
63 | * the goal of adding or removing a certain amount.
64 | *
65 | * @param newBalance the new balance of this account.
66 | * @return result of setting the balance (success or failure type).
67 | */
68 | TransactionResult setBalance(double newBalance);
69 |
70 | /**
71 | * Add an amount to this account's balance.
72 | *
73 | * @param value amount to be added.
74 | * @return result of adding (success or failure type)
75 | */
76 | TransactionResult add(double value);
77 |
78 | /**
79 | * Remove an amount from this account's balance.
80 | *
81 | * @param value amount to be removed
82 | * @return result of removing (success or failure type)
83 | */
84 | TransactionResult remove(double value);
85 |
86 | /**
87 | * Send an amount to another account. If the transfer fails, both sender and recipient will
88 | * have unchanged account balance. To complete the transaction, use the to(Account) method on the result of this
89 | * call.
90 | * Before sending, it is possible to apply taxes with the withTaxes() method.
91 | *
92 | * @param value amount to be transferred
93 | * @return A transaction object, which may be used to complete the transaction or add additional properties.
94 | */
95 | Transaction send(double value);
96 |
97 | /**
98 | * Return the type of this account. Default account types are "player" and "bank".
99 | * The economy plugin specifies any other types.
100 | * The method call Eco.custom(type,id) result in this account for parameters this.type() and this.id().
101 | *
102 | * @return the type of this account.
103 | */
104 | String type();
105 |
106 | /**
107 | * Return the id of this account. For players, this is the player uuid. For banks, this is the name of the bank.
108 | * The method call Eco.custom(type,id) results in this account for parameters this.type() and this.id().
109 | *
110 | * @return the id of this account
111 | */
112 | String id();
113 |
114 | /**
115 | * Send a message to the owner or owners of this account.
116 | * Depending on the type of account, no player is the owner of an account. In this case, send the message to the
117 | * console.
118 | *
119 | * @param message Message to send.
120 | */
121 | void message(String message);
122 |
123 | }
124 |
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/commands/MoneyadminExecutor.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts.commands;
2 |
3 | import org.bukkit.command.Command;
4 | import org.bukkit.command.CommandSender;
5 | import org.gestern.gringotts.api.Account;
6 | import org.gestern.gringotts.api.TransactionResult;
7 |
8 | import static org.gestern.gringotts.Language.LANG;
9 | import static org.gestern.gringotts.api.TransactionResult.SUCCESS;
10 |
11 | /**
12 | * Admin commands for managing ingame aspects.
13 | */
14 | public class MoneyadminExecutor extends GringottsAbstractExecutor {
15 |
16 | @Override
17 | public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args) {
18 | if (args.length < 2) {
19 | return false;
20 | }
21 | String command = args[0];
22 |
23 | // admin command: x of player / faction
24 | if ("b".equalsIgnoreCase(command)) { // You already check if that's true on line 199
25 |
26 | String targetAccountHolderStr = args[1];
27 |
28 | // explicit or automatic account type
29 | Account target;
30 | if (args.length == 3) {
31 | target = eco.custom(args[2], targetAccountHolderStr);
32 | } else {
33 | target = eco.account(targetAccountHolderStr);
34 | }
35 |
36 | if (!target.exists()) {
37 | sendInvalidAccountMessage(sender, targetAccountHolderStr);
38 | return false;
39 | }
40 |
41 | String formattedBalance = eco.currency().format(target.balance());
42 | String senderMessage = LANG.moneyadmin_b
43 | .replace(TAG_BALANCE, formattedBalance)
44 | .replace(TAG_PLAYER, targetAccountHolderStr);
45 |
46 | sender.sendMessage(senderMessage);
47 | return true;
48 | }
49 |
50 | // moneyadmin add/remove
51 | if (args.length >= 3) {
52 | String amountStr = args[1];
53 | double value;
54 |
55 | try {
56 | value = Double.parseDouble(amountStr);
57 | } catch (NumberFormatException ignored) {
58 | return false;
59 | }
60 |
61 | String targetAccountHolderStr = args[2];
62 | Account target;
63 | if (args.length == 4) {
64 | target = eco.custom(args[3], targetAccountHolderStr);
65 | } else {
66 | target = eco.account(targetAccountHolderStr);
67 | }
68 |
69 | if (!target.exists()) {
70 | sendInvalidAccountMessage(sender, targetAccountHolderStr);
71 | return false;
72 | }
73 |
74 | String formatValue = eco.currency().format(value);
75 |
76 | if ("add".equalsIgnoreCase(command)) {
77 | TransactionResult added = target.add(value);
78 | if (added == SUCCESS) {
79 | String senderMessage = LANG.moneyadmin_add_sender.replace(TAG_VALUE, formatValue).replace
80 | (TAG_PLAYER, target.id());
81 |
82 | sender.sendMessage(senderMessage);
83 |
84 | String targetMessage = LANG.moneyadmin_add_target
85 | .replace(TAG_VALUE, formatValue);
86 |
87 | target.message(targetMessage);
88 | } else {
89 | String errorMessage = LANG.moneyadmin_add_error.replace(TAG_VALUE, formatValue).replace
90 | (TAG_PLAYER, target.id());
91 | sender.sendMessage(errorMessage);
92 | }
93 | return true;
94 |
95 | } else if ("rm".equalsIgnoreCase(command)) {
96 | TransactionResult removed = target.remove(value);
97 | if (removed == SUCCESS) {
98 | String senderMessage = LANG.moneyadmin_rm_sender
99 | .replace(TAG_VALUE, formatValue)
100 | .replace(TAG_PLAYER, target.id());
101 |
102 | sender.sendMessage(senderMessage);
103 |
104 | String targetMessage = LANG.moneyadmin_rm_target
105 | .replace(TAG_VALUE, formatValue);
106 |
107 | target.message(targetMessage);
108 | } else {
109 | String errorMessage = LANG.moneyadmin_rm_error
110 | .replace(TAG_VALUE, formatValue)
111 | .replace(TAG_PLAYER, target.id());
112 |
113 | sender.sendMessage(errorMessage);
114 | }
115 | return true;
116 | }
117 | }
118 | return false;
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/AccountInventory.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts;
2 |
3 | import org.bukkit.inventory.Inventory;
4 | import org.bukkit.inventory.ItemStack;
5 | import org.gestern.gringotts.currency.Denomination;
6 | import org.gestern.gringotts.currency.GringottsCurrency;
7 |
8 | import java.util.List;
9 | import java.util.ListIterator;
10 |
11 | import static org.gestern.gringotts.Configuration.CONF;
12 |
13 | /**
14 | * Account inventories define operations that can be used on all inventories belonging to an account.
15 | *
16 | * @author jast
17 | */
18 | public class AccountInventory {
19 |
20 | private final Inventory inventory;
21 |
22 | public AccountInventory(Inventory inventory) {
23 | this.inventory = inventory;
24 | }
25 |
26 | /**
27 | * Current balance of this inventory in cents (or rather atomic currency units).
28 | *
29 | * @return current balance of this inventory in cents
30 | */
31 | public long balance() {
32 | GringottsCurrency cur = CONF.getCurrency();
33 | long count = 0;
34 |
35 | for (ItemStack stack : inventory) {
36 | count += cur.value(stack);
37 | }
38 |
39 | return count;
40 | }
41 |
42 | /**
43 | * Add items to this inventory corresponding to given value.
44 | * If the amount is larger than available space, the space is filled and the actually
45 | * added amount returned.
46 | *
47 | * @param value value to add to this inventory
48 | * @return amount actually added
49 | */
50 | public long add(long value) {
51 | long remaining = value;
52 |
53 | // try denominations from largest to smallest
54 | for (Denomination denom : CONF.getCurrency().getDenominations()) {
55 | if (denom.getValue() <= remaining) {
56 | ItemStack stack = new ItemStack(denom.getKey().type);
57 | int stacksize = stack.getMaxStackSize();
58 | long denomItemCount = denom.getValue() > 0 ? remaining / denom.getValue() : 0;
59 |
60 | // add stacks in this denomination until stuff is returned
61 | while (denomItemCount > 0) {
62 | int remainderStackSize = denomItemCount > stacksize ? stacksize : (int) denomItemCount;
63 | stack.setAmount(remainderStackSize);
64 |
65 | int returned = 0;
66 | for (ItemStack leftover : inventory.addItem(stack).values()) {
67 | returned += leftover.getAmount();
68 | }
69 |
70 | // reduce remaining amount by whatever was deposited
71 | long added = (long) remainderStackSize - returned;
72 | denomItemCount -= added;
73 | remaining -= added * denom.getValue();
74 |
75 | // no more space for this denomination
76 | if (returned > 0) {
77 | break;
78 | }
79 | }
80 | }
81 | }
82 |
83 | return value - remaining;
84 | }
85 |
86 | /**
87 | * Remove items from this inventory corresponding to given value.
88 | *
89 | * @param value amount to remove
90 | * @return value actually removed
91 | */
92 | public long remove(long value) {
93 |
94 | // avoid dealing with negatives
95 | if (value <= 0) {
96 | return 0;
97 | }
98 |
99 | GringottsCurrency cur = CONF.getCurrency();
100 | long remaining = value;
101 |
102 | // try denominations from smallest to largest
103 | List denoms = cur.getDenominations();
104 | for (ListIterator it = denoms.listIterator(denoms.size()); it.hasPrevious(); ) {
105 | Denomination denom = it.previous();
106 | ItemStack stack = new ItemStack(denom.getKey().type);
107 | int stacksize = stack.getMaxStackSize();
108 |
109 | // take 1 more than necessary if it doesn't round. add the extra later
110 | long denomItemCount = (long) Math.ceil((double) remaining / denom.getValue());
111 |
112 | // add stacks in this denomination until stuff is returned or we are done
113 | while (denomItemCount > 0) {
114 | int remainderStackSize = denomItemCount > stacksize ? stacksize : (int) denomItemCount;
115 | stack.setAmount(remainderStackSize);
116 |
117 | int returned = 0;
118 |
119 | for (ItemStack leftover : inventory.removeItem(stack).values()) {
120 | returned += leftover.getAmount();
121 | }
122 |
123 | // reduce remaining amount by whatever was removed
124 | long removed = (long) remainderStackSize - returned;
125 | denomItemCount -= removed;
126 | remaining -= removed * denom.getValue();
127 |
128 | // stuff was returned, no more items of this type to take
129 | if (returned > 0) {
130 | break;
131 | }
132 | }
133 |
134 | }
135 | return value - remaining;
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/Util.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts;
2 |
3 | import org.bukkit.ChatColor;
4 | import org.bukkit.Material;
5 | import org.bukkit.block.Block;
6 | import org.bukkit.block.BlockFace;
7 | import org.bukkit.block.Sign;
8 | import org.bukkit.block.data.BlockData;
9 | import org.bukkit.block.data.type.WallSign;
10 | import org.gestern.gringotts.currency.GringottsCurrency;
11 |
12 | public class Util {
13 |
14 | private Util() {
15 | }
16 |
17 | /**
18 | * Check whether a block is a sign or wall sign type.
19 | *
20 | * @param block block to check
21 | * @return true if the block is a sign or wall sign
22 | */
23 | public static boolean isSignBlock(Block block) {
24 | return block.getState() instanceof Sign;
25 | }
26 |
27 | /**
28 | * Compares whether a version string in standard format (dotted decimals) is greater than another.
29 | *
30 | * @param version version string to check
31 | * @param atLeast minimum expected version
32 | * @return true if version is greater than greaterThanVersion, false otherwise
33 | */
34 | public static boolean versionAtLeast(String version, String atLeast) {
35 | int[] versionParts = versionParts(version);
36 | int[] atLeastParts = versionParts(atLeast);
37 |
38 | for (int i = 0; i < versionParts.length && i < atLeastParts.length; i++) {
39 | // if any more major version part is larger, our version is newer
40 | if (versionParts[i] > atLeastParts[i]) {
41 | return true;
42 | } else if (versionParts[i] < atLeastParts[i]) {
43 | return false;
44 | }
45 | }
46 |
47 | // the at least version has more digits
48 | return atLeastParts.length <= versionParts.length; // supposedly the versions are equal
49 | }
50 |
51 | /**
52 | * Break a version string into parts.
53 | *
54 | * @param version version string to handle
55 | * @return array with dotted decimal strings turned into int values
56 | */
57 | public static int[] versionParts(String version) {
58 | String[] strparts = version.split("\\.");
59 | int[] parts = new int[strparts.length];
60 |
61 | for (int i = 0; i < strparts.length; i++) {
62 | // just cut off any non-number part
63 | String number = strparts[i].replaceAll("(\\d+).*", "$1");
64 | int part = 0;
65 |
66 | try {
67 | part = Integer.parseInt(number);
68 | } catch (NumberFormatException ignored) {
69 | }
70 |
71 | parts[i] = part;
72 | }
73 |
74 | return parts;
75 | }
76 |
77 | /**
78 | * Get a formatted currency value. The value display includes the currency name.
79 | *
80 | * @param value the value in cents
81 | * @return formatted currency value
82 | */
83 | public static String format(double value) {
84 | GringottsCurrency cur = Configuration.CONF.getCurrency();
85 | String formatString = "%." + cur.getDigits() + "f %s";
86 |
87 | return String.format(formatString, value, value == 1.0 ? cur.getName() : cur.getNamePlural());
88 | }
89 |
90 | /**
91 | * Find a valid container block for a given sign, if it exists.
92 | *
93 | * @param sign sign to check
94 | * @return container for the sign if available, null otherwise.
95 | */
96 | public static Block chestBlock(Sign sign) {
97 | // is sign attached to a valid vault container?
98 | Block signBlock = sign.getBlock();
99 | BlockData blockData = signBlock.getBlockData();
100 |
101 | if (!(blockData instanceof WallSign)) {
102 | return null;
103 | }
104 |
105 | WallSign signData = (WallSign) blockData;
106 | BlockFace attached = signData.getFacing().getOppositeFace();
107 |
108 | // allow either the block sign is attached to or the block below the sign as chest block. Prefer attached block.
109 | Block blockAttached = signBlock.getRelative(attached);
110 | Block blockBelow = signBlock.getRelative(BlockFace.DOWN);
111 |
112 | return validContainer(blockAttached.getType()) ? blockAttached : validContainer(blockBelow.getType()) ? blockBelow : null;
113 | }
114 |
115 | /**
116 | * Return whether the given material is a valid container type for Gringotts vaults.
117 | *
118 | * @param material material to check
119 | * @return whether the given material is a valid container type for Gringotts vaults
120 | */
121 | public static boolean validContainer(Material material) {
122 | switch (material) {
123 | case CHEST:
124 | case TRAPPED_CHEST:
125 | case DISPENSER:
126 | case FURNACE:
127 | case HOPPER:
128 | case DROPPER:
129 | case BARREL:
130 | return true;
131 | default:
132 | return false;
133 | }
134 | }
135 |
136 | /**
137 | * Alias for color code translation. Uses '&' as code prefix.
138 | *
139 | * @param s String to translate color codes.
140 | * @return the translated String
141 | */
142 | public static String translateColors(String s) {
143 | return ChatColor.translateAlternateColorCodes('&', s);
144 | }
145 |
146 | public static String reformMaterialName(Material material) {
147 | String name = material.name();
148 | String[] words = name.split("_");
149 |
150 | for (int i = 0; i < words.length; i++) {
151 | words[i] = words[i].substring(0, 1).toUpperCase() + words[i].substring(1).toLowerCase();
152 | }
153 |
154 | return String.join(" ", words);
155 | }
156 | }
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/commands/GringottsAbstractExecutor.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts.commands;
2 |
3 | import org.bukkit.command.CommandExecutor;
4 | import org.bukkit.command.CommandSender;
5 | import org.bukkit.entity.Player;
6 | import org.gestern.gringotts.Configuration;
7 | import org.gestern.gringotts.Gringotts;
8 | import org.gestern.gringotts.Permissions;
9 | import org.gestern.gringotts.api.*;
10 |
11 | import static org.gestern.gringotts.Language.LANG;
12 | import static org.gestern.gringotts.Permissions.COMMAND_DEPOSIT;
13 | import static org.gestern.gringotts.Permissions.COMMAND_WITHDRAW;
14 | import static org.gestern.gringotts.api.TransactionResult.SUCCESS;
15 |
16 | public abstract class GringottsAbstractExecutor implements CommandExecutor {
17 | static final String TAG_BALANCE = "%balance";
18 |
19 | static final String TAG_PLAYER = "%player";
20 |
21 | static final String TAG_VALUE = "%value";
22 |
23 | final Gringotts plugin = Gringotts.getInstance();
24 | final Eco eco = plugin.getEco();
25 |
26 | static void sendInvalidAccountMessage(CommandSender sender, String accountName) {
27 | sender.sendMessage(LANG.invalid_account.replace(TAG_PLAYER, accountName));
28 | }
29 |
30 | boolean pay(Player player, double value, String[] args) {
31 | if (!Permissions.TRANSFER.allowed(player)) {
32 | player.sendMessage(LANG.noperm);
33 |
34 | return true;
35 | }
36 |
37 | String recipientName = args[2];
38 |
39 | Account from = eco.player(player.getUniqueId());
40 | Account to = eco.account(recipientName);
41 |
42 | PlayerAccount playerAccount = eco.player(player.getUniqueId());
43 |
44 | TaxedTransaction transaction = playerAccount.send(value).withTaxes();
45 | TransactionResult result = playerAccount.send(value).withTaxes().to(eco.player(recipientName));
46 |
47 | double tax = transaction.tax();
48 | double valueAdded = value + tax;
49 |
50 | String formattedBalance = eco.currency().format(from.balance());
51 | String formattedValue = eco.currency().format(value);
52 | String formattedValuePlusTax = eco.currency().format(valueAdded);
53 | String formattedTax = eco.currency().format(tax);
54 |
55 | switch (result) {
56 | case SUCCESS:
57 | String succTaxMessage = LANG.pay_success_tax.replace(TAG_VALUE, formattedTax);
58 | String succSentMessage = LANG.pay_success_sender.replace(TAG_VALUE, formattedValue).replace
59 | (TAG_PLAYER, recipientName);
60 | from.message(succSentMessage + (tax > 0 ? succTaxMessage : ""));
61 | String succReceivedMessage = LANG.pay_success_target.replace(TAG_VALUE, formattedValue).replace
62 | (TAG_PLAYER, player.getName());
63 | to.message(succReceivedMessage);
64 | return true;
65 | case INSUFFICIENT_FUNDS:
66 | String insFMessage = LANG.pay_insufficientFunds.replace(TAG_BALANCE, formattedBalance).replace
67 | (TAG_VALUE, formattedValuePlusTax);
68 | from.message(insFMessage);
69 | return true;
70 | case INSUFFICIENT_SPACE:
71 | String insSSentMessage = LANG.pay_insS_sender.replace(TAG_PLAYER, recipientName).replace(TAG_VALUE,
72 | formattedValue);
73 | from.message(insSSentMessage);
74 | String insSReceiveMessage = LANG.pay_insS_target.replace(TAG_PLAYER, from.id()).replace(TAG_VALUE,
75 | formattedValue);
76 | to.message(insSReceiveMessage);
77 | return true;
78 | default:
79 | String error = LANG.pay_error.replace(TAG_VALUE, formattedValue).replace(TAG_PLAYER, recipientName);
80 | from.message(error);
81 | return true;
82 | }
83 | }
84 |
85 | void deposit(Player player, double value) {
86 |
87 | if (COMMAND_DEPOSIT.allowed(player)) {
88 | TransactionResult result = eco.player(player.getUniqueId()).deposit(value);
89 | String formattedValue = eco.currency().format(value);
90 |
91 | if (result == SUCCESS) {
92 | String success = LANG.deposit_success.replace(TAG_VALUE, formattedValue);
93 | player.sendMessage(success);
94 | } else {
95 | String error = LANG.deposit_error.replace(TAG_VALUE, formattedValue);
96 | player.sendMessage(error);
97 | }
98 | }
99 | }
100 |
101 | void withdraw(Player player, double value) {
102 | if (COMMAND_WITHDRAW.allowed(player)) {
103 | TransactionResult result = eco.player(player.getUniqueId()).withdraw(value);
104 | String formattedValue = eco.currency().format(value);
105 | if (result == SUCCESS) {
106 | String success = LANG.withdraw_success.replace(TAG_VALUE, formattedValue);
107 | player.sendMessage(success);
108 | } else {
109 | String error = LANG.withdraw_error.replace(TAG_VALUE, formattedValue);
110 | player.sendMessage(error);
111 | }
112 | }
113 | }
114 |
115 | void sendBalanceMessage(Account account) {
116 |
117 | account.message(LANG.balance.replace(TAG_BALANCE, eco.currency().format(account.balance())));
118 |
119 | if (Configuration.CONF.balanceShowVault) {
120 | account.message(LANG.vault_balance.replace(TAG_BALANCE, eco.currency().format(account.vaultBalance())));
121 | }
122 |
123 | if (Configuration.CONF.balanceShowInventory) {
124 | account.message(LANG.inv_balance.replace(TAG_BALANCE, eco.currency().format(account.invBalance())));
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Gringotts
2 | =========
3 |
4 | [](https://gitter.im/MinecraftWars/Gringotts?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
5 | [](https://travis-ci.org/MinecraftWars/Gringotts)
6 | [](https://jitpack.io/#MinecraftWars/Gringotts)
7 |
8 | Gringotts is an item-based economy plugin for the Bukkit Minecraft server platform. Unlike earlier economy plugins, all currency value and money transactions are based on actual items in Minecraft, per default emeralds. The goals are to add a greater level of immersion, a generally more Minecraft-like feeling, and in the case of a PvP environment, making the currency itself vulnerable to raiding.
9 |
10 |
11 | [Get Gringotts from BukkitDev](https://dev.bukkit.org/projects/gringotts) or
12 | [get Gringotts from Spigot](https://www.spigotmc.org/resources/gringotts.42071/)!
13 |
14 | Looking for maintainers!
15 | ------------------------
16 |
17 | I initially created Gringotts a long time ago for a server that no longer exists, and I don't play Minecraft much anymore,
18 | and have many other interesting things to do. Thus, I can't devote much time to the project. I would like to hand the
19 | project off to somebody interested in its continued existence.
20 |
21 | ### How to become a maintainer? ###
22 |
23 | 1. Open a pull request with some kind of update for Gringotts:
24 | * update for a new version of Bukkit
25 | * add a feature
26 | * fix a bug
27 | * update documentation
28 | 2. Say that you would like to be a maintainer.
29 | 3. If the PR is reasonable, I will give you commit access to the GitHub repository and the bukkitdev project.
30 |
31 | Features
32 | --------
33 | * Item-backed economy (configurable, default emeralds)
34 | * Multiple denominations with automatic conversion (for example, use emeralds and emerald blocks)
35 | * Storage of currency in chests and other containers, player inventory and ender chests (configurable)
36 | * Direct account-to-account transfers commands
37 | * Optional transaction taxes
38 | * Fractional currency values (fixed decimal digits)
39 | * Account support for [Factions](http://dev.bukkit.org/server-mods/factions/), [Towny](http://dev.bukkit.org/server-mods/towny-advanced/) and [WorldGuard](http://dev.bukkit.org/server-mods/worldguard/)
40 | * [Vault](http://dev.bukkit.org/server-mods/vault/) integration
41 |
42 | Usage
43 | -----
44 | Storing money in an account requires a Gringotts vault. A vault consists of a container, which can be either chest, dispenser or furnace, and a sign above or on it declaring it as a vault. A player or faction may claim any number of vaults. Vaults are not protected from access through other players. If you would like them to be, you may use additional plugins such as [LWC](http://dev.bukkit.org/server-mods/lwc/) or [WorldGuard](http://dev.bukkit.org/server-mods/worldguard/).
45 |
46 | For full usage documentation, please see [the usage page](https://github.com/MinecraftWars/Gringotts/blob/master/doc/usage.md)
47 |
48 | ### Player vaults ###
49 |
50 | Place a sign above a container block, with `[vault]` written on the first line. If it was created correctly, the sign will display your name on the third line and you will receive a message that the vault has been created.
51 |
52 | ### Faction vaults ###
53 |
54 | Place a sign above a container block, with `[faction vault]` written on the first line. If it was created correctly, the sign will display your faction's tag on the third line and you will receive a message that the vault has been created.
55 |
56 | ### Towny vaults ###
57 |
58 | * To make a vault for your town: place a sign above a container block, with `[town vault]` written on the first line.
59 | * To make a vault for your nation: place a sign above a container block, with `[nation vault]` written on the first line.
60 | If it was created correctly, the sign will display your town's or nation's tag on the third line and you will receive a message that the vault has been created.
61 |
62 | Commands
63 | --------
64 | See [Usage](https://github.com/MinecraftWars/Gringotts/blob/master/doc/usage.md#commands).
65 |
66 | Installation and Configuration
67 | ------------------------------
68 | Download [Gringotts](http://dev.bukkit.org/server-mods/gringotts/files/) and place it in your craftbukkit/plugins folder
69 |
70 | Please see the [Configuration and Permissions](https://github.com/MinecraftWars/Gringotts/blob/master/doc/configuration.md) document on how to configure Gringotts.
71 |
72 | Problems? Questions?
73 | --------------------
74 | Have a look at the [Wiki](https://github.com/MinecraftWars/Gringotts/wiki). You're welcome to improve it, too!
75 |
76 |
77 | Development
78 | -----------
79 | Would you like to make changes to Gringotts yourself? Fork it!
80 | Pull requests are very welcome, but please make sure your changes fulfill the Gringotts quality baseline:
81 |
82 | * new features, settings, permissions are documented
83 | * required dependencies are all added to the build by Maven, not included in the repo
84 | * the project builds with Maven out-of-the-box
85 |
86 | Gringotts uses the [Apache Maven](http://maven.apache.org/) build system. Build a working plugin jar with the command:
87 |
88 | ```bash
89 | mvn compile package
90 | ```
91 |
92 | This shades in some dependencies (such as plugin metrics). For this reason, creating a jar package manually or from an IDE may not work correctly.
93 |
94 |
95 | Depending on Gringotts
96 | -----------
97 | Gringotts makes use of JitPack to provide itself as a just-in-time compiled Maven dependency.
98 |
99 | #### Step 1
100 | Add the JitPack repository to your project's build system.
101 | > See [JitPack.io](https://jitpack.io) for boilerplate examples.
102 |
103 | Supported build systems:
104 | - **Apache Maven** — [Maven POM Reference: Repositories](https://maven.apache.org/pom.html#Repositories)
105 | - **Gradle** — [Gradle User Guide: Declaring Repositories](https://docs.gradle.org/current/userguide/declaring_repositories.html)
106 |
107 | #### Step 2
108 | Add the dependency.
109 | See your documentation, or the boilerplate on JitPack.io, for dependency declaration instructions.
110 | An example is given below for Apache Maven.
111 |
112 | Replace the `RELEASE_TAG` with a valid [tagged commit](https://github.com/MinecraftWars/Gringotts/tags), or use
113 | `master-SNAPSHOT` to track the master branch.
114 | Note that Snapshots may require extra configuration of your repository.
115 |
116 | ```xml
117 |
118 | com.github.MinecraftWars
119 | Gringotts
120 | RELEASE_TAG
121 | provided
122 |
123 | ```
124 |
125 | License
126 | -------
127 | All code within Gringotts is licensed under the BSD 2-clause license. See [the LICENSE.txt file](./LICENSE.txt) for details.
128 |
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/currency/GringottsCurrency.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts.currency;
2 |
3 | import org.apache.commons.lang.StringUtils;
4 | import org.bukkit.Material;
5 | import org.bukkit.inventory.ItemStack;
6 |
7 | import java.util.*;
8 |
9 | /**
10 | * Representation of a currency. This contains information about the currency's denominations and their values.
11 | * The value is represented internally as "cents", that is, the smallest currency unit, and only gets transformed
12 | * into display value
13 | * for communication with the user or vault.
14 | *
15 | * @author jast
16 | */
17 | public class GringottsCurrency {
18 |
19 | /**
20 | * Name of the currency.
21 | */
22 | private final String name;
23 | /**
24 | * Name of the currency, plural version.
25 | */
26 | private final String namePlural;
27 | /**
28 | * Currency unit divisor. Internally, all calculation is done in "cents".
29 | * This multiplier changes the external representation.
30 | * For instance, with unit 100, every cent will be worth 0.01 currency units
31 | */
32 | private final int unit;
33 | /**
34 | * Fractional digits supported by this currency.
35 | * For example, with 2 digits the minimum currency value would be 0.01
36 | */
37 | private final int digits;
38 | /**
39 | * Show balances and other currency values with individual denomination names.
40 | */
41 | private final boolean namedDenominations;
42 | private final Map denoms = new HashMap<>();
43 | private final List sortedDenoms = new ArrayList<>();
44 |
45 | /**
46 | * Create currency.
47 | *
48 | * @param name name of currency
49 | * @param namePlural plural of currency name
50 | * @param digits decimal digits used in currency
51 | */
52 | public GringottsCurrency(String name, String namePlural, int digits, boolean namedDenominations) {
53 | this.name = name;
54 | this.namePlural = namePlural;
55 | this.digits = digits;
56 | this.namedDenominations = namedDenominations;
57 |
58 | // calculate the "unit" from digits. It's just a power of 10!
59 | int d = digits, u = 1;
60 | while (d-- > 0) u *= 10;
61 | this.unit = u;
62 | }
63 |
64 | /**
65 | * Add a denomination and value to this currency.
66 | *
67 | * @param type the denomination's item type
68 | * @param value the denomination's value
69 | */
70 | public void addDenomination(ItemStack type, double value, String unitName, String unitNamePlural) {
71 | DenominationKey k = new DenominationKey(type);
72 | Denomination d = new Denomination(k, centValue(value), unitName, unitNamePlural);
73 | denoms.put(k, d);
74 | // infrequent insertion, so I don't mind sorting on every insert
75 | sortedDenoms.add(d);
76 | Collections.sort(sortedDenoms);
77 | }
78 |
79 |
80 | /**
81 | * Get the value of an item stack in cents.
82 | * This is calculated by value_type * stacksize.
83 | * If the given item stack is not a valid denomination, the value is 0;
84 | *
85 | * @param stack a stack of items
86 | * @return the value of given stack of items
87 | */
88 | public long value(ItemStack stack) {
89 | if (stack == null || stack.getType() == Material.AIR) {
90 | return 0;
91 | }
92 |
93 | Denomination d = denominationOf(stack);
94 |
95 | return d != null ? d.getValue() * stack.getAmount() : 0;
96 | }
97 |
98 | /**
99 | * The display value for a given cent value.
100 | *
101 | * @param value value to calculate display value for
102 | * @return user representation of value
103 | */
104 | public double displayValue(long value) {
105 | return (double) value / unit;
106 | }
107 |
108 | /**
109 | * The internal calculation value of a display value.
110 | *
111 | * @param value display value
112 | * @return Gringotts-internal value of given amount
113 | */
114 | public long centValue(double value) {
115 | return Math.round(value * unit);
116 | }
117 |
118 |
119 | /**
120 | * List of denominations used in this currency, in order of descending value.
121 | *
122 | * @return Unmodifiable List of denominations used in this currency, in order of descending value
123 | */
124 | public List getDenominations() {
125 | return Collections.unmodifiableList(sortedDenoms);
126 | }
127 |
128 | public String format(String formatString, double value) {
129 |
130 | if (namedDenominations) {
131 |
132 | StringBuilder b = new StringBuilder();
133 |
134 | long cv = centValue(value);
135 |
136 | for (Denomination denom : sortedDenoms) {
137 | long dv = cv / denom.getValue();
138 | cv %= denom.getValue();
139 |
140 | if (dv > 0) {
141 | String display = dv + " " + (dv == 1L ? denom.getUnitName() : denom.getUnitNamePlural());
142 | b.append(display);
143 |
144 | if (cv > 0) {
145 | b.append(", ");
146 | }
147 | }
148 | }
149 |
150 | // might need this check for fractional values
151 | if (cv > 0 || b.length() == 0) {
152 | double displayVal = displayValue(cv);
153 |
154 | b.append(String.format(formatString, displayVal, displayVal == 1.0 ? name : namePlural));
155 | }
156 |
157 | return b.toString();
158 |
159 | } else return String.format(formatString, value, value == 1.0 ? name : namePlural);
160 |
161 | }
162 |
163 | /**
164 | * Get the denomination of an item stack.
165 | *
166 | * @param stack the stack to get the denomination for
167 | * @return denomination for the item stack, or null if there is no such denomination
168 | */
169 | private Denomination denominationOf(ItemStack stack) {
170 | DenominationKey d = new DenominationKey(stack);
171 |
172 | return denoms.get(d);
173 | }
174 |
175 | @Override
176 | public String toString() {
177 | return StringUtils.join(sortedDenoms, '\n');
178 | }
179 |
180 | /**
181 | * Fractional digits supported by this currency.
182 | * For example, with 2 digits the minimum currency value would be 0.01
183 | */
184 | public int getDigits() {
185 | return digits;
186 | }
187 |
188 | /**
189 | * Currency unit divisor. Internally, all calculation is done in "cents".
190 | * This multiplier changes the external representation.
191 | * For instance, with unit 100, every cent will be worth 0.01 currency units
192 | */
193 | public int getUnit() {
194 | return unit;
195 | }
196 |
197 | /**
198 | * Name of the currency.
199 | */
200 | public String getName() {
201 | return name;
202 | }
203 |
204 | /**
205 | * Name of the currency, plural version.
206 | */
207 | public String getNamePlural() {
208 | return namePlural;
209 | }
210 | }
211 |
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/dependency/WorldGuardHandler.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts.dependency;
2 |
3 | import com.sk89q.worldedit.bukkit.BukkitWorld;
4 | import com.sk89q.worldguard.WorldGuard;
5 | import com.sk89q.worldguard.bukkit.WorldGuardPlugin;
6 | import com.sk89q.worldguard.domains.DefaultDomain;
7 | import com.sk89q.worldguard.internal.platform.WorldGuardPlatform;
8 | import com.sk89q.worldguard.protection.managers.RegionManager;
9 | import com.sk89q.worldguard.protection.regions.ProtectedRegion;
10 | import org.bukkit.Bukkit;
11 | import org.bukkit.World;
12 | import org.bukkit.entity.Player;
13 | import org.bukkit.event.EventHandler;
14 | import org.bukkit.event.Listener;
15 | import org.bukkit.plugin.Plugin;
16 | import org.gestern.gringotts.Gringotts;
17 | import org.gestern.gringotts.accountholder.AccountHolder;
18 | import org.gestern.gringotts.accountholder.AccountHolderProvider;
19 | import org.gestern.gringotts.event.PlayerVaultCreationEvent;
20 | import org.gestern.gringotts.event.VaultCreationEvent.Type;
21 |
22 | import java.util.UUID;
23 |
24 | import static org.gestern.gringotts.Language.LANG;
25 | import static org.gestern.gringotts.Permissions.CREATEVAULT_ADMIN;
26 | import static org.gestern.gringotts.Permissions.CREATEVAULT_WORLDGUARD;
27 |
28 | public abstract class WorldGuardHandler implements DependencyHandler, AccountHolderProvider {
29 | public static WorldGuardHandler getWorldGuardHandler(Plugin plugin) {
30 | if (plugin instanceof WorldGuardPlugin) {
31 | return new ValidWorldGuardHandler((WorldGuardPlugin) plugin);
32 | } else {
33 | return new InvalidWorldGuardHandler();
34 | }
35 | }
36 | }
37 |
38 | class InvalidWorldGuardHandler extends WorldGuardHandler {
39 | @Override
40 | public AccountHolder getAccountHolder(String id) {
41 | return null;
42 | }
43 |
44 | @Override
45 | public boolean enabled() {
46 | return false;
47 | }
48 |
49 | @Override
50 | public boolean exists() {
51 | return false;
52 | }
53 | }
54 |
55 | class ValidWorldGuardHandler extends WorldGuardHandler {
56 |
57 | private WorldGuardPlugin plugin;
58 |
59 | public ValidWorldGuardHandler(WorldGuardPlugin plugin) {
60 | this.plugin = plugin;
61 |
62 | Bukkit.getPluginManager().registerEvents(new WorldGuardListener(), Gringotts.getInstance());
63 | Gringotts.getInstance().registerAccountHolderProvider("worldguard", this);
64 |
65 | }
66 |
67 |
68 | @Override
69 | public boolean enabled() {
70 | if (plugin != null) {
71 | return plugin.isEnabled();
72 | } else return false;
73 | }
74 |
75 | @Override
76 | public boolean exists() {
77 | return plugin != null;
78 | }
79 |
80 |
81 | @Override
82 | public WorldGuardAccountHolder getAccountHolder(String id) {
83 | // FIXME use something more robust than - as world-id delimiter
84 | // try explicit world+id first
85 | String[] parts = id.split("-", 2);
86 | if (parts.length == 2) {
87 | WorldGuardAccountHolder wgah = getAccountHolder(parts[0], parts[1]);
88 |
89 | if (wgah != null) {
90 | return wgah;
91 | }
92 | }
93 |
94 | // try bare id in all worlds
95 | WorldGuardPlatform worldguardPlatform = WorldGuard.getInstance().getPlatform();
96 | for (World world : Bukkit.getWorlds()) {
97 |
98 | RegionManager worldManager = worldguardPlatform.getRegionContainer().get(new BukkitWorld(world));
99 |
100 | if (worldManager != null && worldManager.hasRegion(id)) {
101 | ProtectedRegion region = worldManager.getRegion(id);
102 |
103 | return new WorldGuardAccountHolder(world.getName(), region);
104 | }
105 | }
106 |
107 | return null;
108 | }
109 |
110 | /**
111 | * Get account holder for known world and region id.
112 | *
113 | * @param world name of world
114 | * @param id worldguard region id
115 | * @return account holder for the region
116 | */
117 | public WorldGuardAccountHolder getAccountHolder(String world, String id) {
118 | World w = Bukkit.getWorld(world);
119 |
120 | if (w == null) {
121 | return null;
122 | }
123 |
124 | WorldGuardPlatform worldguardPlatform = WorldGuard.getInstance().getPlatform();
125 | RegionManager worldManager = worldguardPlatform.getRegionContainer().get(new BukkitWorld(w));
126 |
127 | if (worldManager == null) {
128 | return null;
129 | }
130 |
131 | if (worldManager.hasRegion(id)) {
132 | ProtectedRegion region = worldManager.getRegion(id);
133 |
134 | return new WorldGuardAccountHolder(world, region);
135 | }
136 |
137 | return null;
138 | }
139 |
140 | public class WorldGuardListener implements Listener {
141 |
142 | @EventHandler
143 | public void vaultCreated(PlayerVaultCreationEvent event) {
144 | // some listener already claimed this event
145 | if (event.isValid()) return;
146 |
147 | if (event.getType() == Type.REGION) {
148 | Player player = event.getCause().getPlayer();
149 | if (!CREATEVAULT_WORLDGUARD.allowed(player)) {
150 | player.sendMessage(LANG.plugin_worldguard_noVaultPerm);
151 |
152 | return;
153 | }
154 |
155 | String regionId = event.getCause().getLine(2);
156 | String[] regionComponents = regionId.split("-", 1);
157 |
158 | WorldGuardAccountHolder owner;
159 | if (regionComponents.length == 1) {
160 | // try to guess the world
161 | owner = getAccountHolder(regionComponents[0]);
162 | } else {
163 | String world = regionComponents[0];
164 | String id = regionComponents[1];
165 | owner = getAccountHolder(world, id);
166 | }
167 |
168 | if (owner != null && (owner.region.hasMembersOrOwners() || CREATEVAULT_ADMIN.allowed(player))) {
169 | DefaultDomain regionOwners = owner.region.getOwners();
170 | if (regionOwners.contains(player.getName())) {
171 | event.setOwner(owner);
172 | event.setValid(true);
173 | }
174 | }
175 | }
176 | }
177 | }
178 | }
179 |
180 | class WorldGuardAccountHolder implements AccountHolder {
181 |
182 | final String world;
183 | final ProtectedRegion region;
184 |
185 | public WorldGuardAccountHolder(String world, ProtectedRegion region) {
186 | this.world = world;
187 | this.region = region;
188 | }
189 |
190 | @Override
191 | public String getName() {
192 | return region.getId();
193 | }
194 |
195 | @Override
196 | public void sendMessage(String message) {
197 | //Send the message to owners.
198 | for (UUID uuid : region.getOwners().getUniqueIds()) {
199 | Player player = Bukkit.getPlayer(uuid);
200 |
201 | if (player != null) {
202 | player.sendMessage(message);
203 | }
204 | }
205 |
206 | //Send the message to members.
207 | for (UUID uuid : region.getMembers().getUniqueIds()) {
208 | Player player = Bukkit.getPlayer(uuid);
209 |
210 | if (player != null) {
211 | player.sendMessage(message);
212 | }
213 | }
214 | }
215 |
216 | @Override
217 | public String getType() {
218 | return "worldguard";
219 | }
220 |
221 | @Override
222 | public String getId() {
223 | return world + "-" + region.getId();
224 | }
225 | }
226 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 | minecraftwars
5 | Gringotts
6 | 2.11.1-SNAPSHOT
7 |
8 |
9 |
10 |
11 | ${basedir}
12 | true
13 |
14 | *.yml
15 | LICENSE.txt
16 | README.md
17 |
18 |
19 |
20 | src/main/resources/i18n
21 | i18n
22 |
23 | *.yml
24 |
25 |
26 |
27 |
28 |
29 |
30 | org.apache.maven.plugins
31 | maven-compiler-plugin
32 | 3.8.0
33 |
34 | 1.8
35 | 1.8
36 |
37 |
38 |
39 | org.apache.maven.plugins
40 | maven-jar-plugin
41 | 3.1.1
42 |
43 |
44 |
45 |
46 |
47 | true
48 | custom
49 |
50 |
51 | ../lib/$${artifact.artifactId}-$${artifact.version}$${dashClassifier?}.$${artifact.extension}
52 |
53 |
54 |
55 |
56 |
57 |
58 | org.apache.maven.plugins
59 | maven-shade-plugin
60 | 3.2.1
61 |
62 |
63 |
64 | org.bstats
65 | org.gestern.gringotts
66 |
67 |
68 |
69 |
70 |
71 | package
72 |
73 | shade
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 | org.spigotmc
84 | spigot-api
85 | 1.14.1-R0.1-SNAPSHOT
86 | provided
87 |
88 |
89 | com.github.TownyAdvanced
90 | Towny
91 | 0.96.7.0
92 | provided
93 | true
94 |
95 |
96 | org.bstats
97 | bstats-bukkit
98 | 1.8
99 | compile
100 |
101 |
102 | net.milkbowl.vault
103 | VaultAPI
104 | 1.7
105 | provided
106 |
107 |
108 | com.massivecraft
109 | mcore
110 | 2.12.0
111 | provided
112 |
113 |
114 | com.massivecraft
115 | factions
116 | 2.12.0
117 | provided
118 |
119 |
120 | org.junit.jupiter
121 | junit-jupiter-engine
122 | ${junit.jupiter.version}
123 | test
124 |
125 |
126 | org.avaje
127 | ebean
128 | 2.8.1
129 | compile
130 |
131 |
132 | com.sk89q.worldguard
133 | worldguard-legacy
134 | 6.2
135 | provided
136 |
137 |
138 | com.sk89q.worldguard
139 | worldguard-core
140 | 7.0.4
141 | provided
142 |
143 |
144 | com.sk89q.worldedit
145 | worldedit-bukkit
146 | 7.2.2
147 | provided
148 |
149 |
150 |
151 |
152 | UTF-8
153 | 5.7.1
154 |
155 |
156 |
157 | https://github.com/MinecraftWars/Gringotts
158 | scm:git:git://github.com:MinecraftWars/Gringotts.git
159 | scm:git:git@github.com:MinecraftWars/Gringotts.git
160 |
161 |
162 | GitHub
163 | https://github.com/MinecraftWars/Gringotts/issues
164 |
165 |
166 |
167 | jitpack
168 | https://jitpack.io
169 |
170 |
171 | CodeMC
172 | https://repo.codemc.org/repository/maven-public
173 |
174 |
175 | spigot-repo
176 | https://hub.spigotmc.org/nexus/content/groups/public/
177 |
178 | true
179 |
180 |
181 | true
182 |
183 |
184 |
185 | enginehub
186 | http://maven.enginehub.org/repo/
187 |
188 |
189 | vault-repo
190 | http://nexus.hc.to/content/repositories/pub_releases
191 |
192 | true
193 |
194 |
195 | false
196 |
197 |
198 |
199 | project.local
200 | project.local
201 |
202 | true
203 | ignore
204 |
205 | file://${project.basedir}/repo
206 |
207 |
208 |
209 |
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/dependency/FactionsHandler.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts.dependency;
2 |
3 | import com.massivecraft.factions.Factions;
4 | import com.massivecraft.factions.entity.Faction;
5 | import com.massivecraft.factions.entity.FactionColl;
6 | import com.massivecraft.factions.entity.MPlayer;
7 | import org.bukkit.Bukkit;
8 | import org.bukkit.entity.Player;
9 | import org.bukkit.event.EventHandler;
10 | import org.bukkit.event.Listener;
11 | import org.bukkit.plugin.Plugin;
12 | import org.gestern.gringotts.Gringotts;
13 | import org.gestern.gringotts.accountholder.AccountHolder;
14 | import org.gestern.gringotts.accountholder.AccountHolderProvider;
15 | import org.gestern.gringotts.event.PlayerVaultCreationEvent;
16 | import org.gestern.gringotts.event.VaultCreationEvent.Type;
17 |
18 | import static org.gestern.gringotts.Language.LANG;
19 | import static org.gestern.gringotts.Permissions.CREATEVAULT_ADMIN;
20 | import static org.gestern.gringotts.Permissions.CREATEVAULT_FACTION;
21 | import static org.gestern.gringotts.dependency.Dependency.DEP;
22 |
23 | public abstract class FactionsHandler implements DependencyHandler, AccountHolderProvider {
24 | /**
25 | * Get a valid Factions handler if the plugin instance is valid. Otherwise get a fake one.
26 | * This is needed because HCFactions is a fork of Factions that uses some of the same classes,
27 | * but trying to load it causes errors.
28 | *
29 | * @param factions Factions plugin instance
30 | * @return a Factions handler
31 | */
32 | public static FactionsHandler getFactionsHandler(Plugin factions) {
33 | if (factions instanceof Factions) {
34 | return new ValidFactionsHandler((Factions) factions);
35 | } else {
36 | Gringotts.getInstance().getLogger().warning(
37 | "Unable to load Factions handler because your version of Factions " +
38 | "is not compatible with Gringotts. Factions support will not work");
39 |
40 | return new InvalidFactionsHandler();
41 | }
42 | }
43 |
44 | public abstract FactionAccountHolder getFactionAccountHolder(Player player);
45 |
46 | public abstract FactionAccountHolder getAccountHolderById(String id);
47 | }
48 |
49 | class InvalidFactionsHandler extends FactionsHandler {
50 |
51 | @Override
52 | public FactionAccountHolder getFactionAccountHolder(Player player) {
53 | return null;
54 | }
55 |
56 | @Override
57 | public FactionAccountHolder getAccountHolderById(String id) {
58 | return null;
59 | }
60 |
61 | @Override
62 | public AccountHolder getAccountHolder(String id) {
63 | return null;
64 | }
65 |
66 | @Override
67 | public boolean enabled() {
68 | return false;
69 | }
70 |
71 | @Override
72 | public boolean exists() {
73 | return false;
74 | }
75 | }
76 |
77 | class ValidFactionsHandler extends FactionsHandler {
78 |
79 | private final Factions plugin;
80 |
81 | public ValidFactionsHandler(Factions plugin) {
82 | this.plugin = plugin;
83 |
84 | if (plugin != null) {
85 | Bukkit.getPluginManager().registerEvents(new FactionsListener(), Gringotts.getInstance());
86 | Gringotts.getInstance().registerAccountHolderProvider("faction", this);
87 | }
88 | }
89 |
90 | /**
91 | * Get a FactionAccountHolder for the faction of which player is a member, if any.
92 | *
93 | * @param player player to get the faction for
94 | * @return FactionAccountHolder for the faction of which player is a member, if any. null otherwise.
95 | */
96 | @Override
97 | public FactionAccountHolder getFactionAccountHolder(Player player) {
98 | Faction playerFaction = MPlayer.get(player).getFaction();
99 |
100 | return playerFaction != null ? new FactionAccountHolder(playerFaction) : null;
101 | }
102 |
103 | /**
104 | * Get a FactionAccountHolder by id of the faction.
105 | *
106 | * @param id id to get the faction for
107 | * @return faction account holder for given id
108 | */
109 | @Override
110 | public FactionAccountHolder getAccountHolderById(String id) {
111 | Faction faction = FactionColl.get().getFixed(id);
112 |
113 | return faction != null ? new FactionAccountHolder(faction) : null;
114 | }
115 |
116 | @Override
117 | public boolean enabled() {
118 | return plugin != null && plugin.isEnabled();
119 | }
120 |
121 | @Override
122 | public boolean exists() {
123 | return plugin != null;
124 | }
125 |
126 | /**
127 | * Get a FactionAccountHolder based on the name of the account.
128 | * Valid ids for this method are either raw faction ids, or faction ids or tags prefixed with "faction-"
129 | * Only names beginning with "faction-" will be considered, and the rest of the string
130 | * can be either a faction id or a faction tag.
131 | *
132 | * @param id Name of the account.
133 | * @return a FactionAccountHolder based on the name of the account, if a valid faction could be found. null
134 | * otherwise.
135 | */
136 | @Override
137 | public FactionAccountHolder getAccountHolder(String id) {
138 |
139 | String factionId = id;
140 | if (id.startsWith("faction-")) {
141 | // requested a prefixed id, cut off the prefix!
142 | factionId = id.substring(8);
143 | }
144 |
145 | // first try raw id
146 | FactionAccountHolder owner = getAccountHolderById(factionId);
147 | if (owner != null) {
148 | return owner;
149 | }
150 |
151 | // just in case, also try the tag
152 | Faction faction = FactionColl.get().getFixed(id);
153 |
154 | if (faction != null) {
155 | return new FactionAccountHolder(faction);
156 | }
157 |
158 | return null;
159 | }
160 | }
161 |
162 | class FactionsListener implements Listener {
163 |
164 | @EventHandler
165 | public void vaultCreated(PlayerVaultCreationEvent event) {
166 | // some listener already claimed this event
167 | if (event.isValid()) return;
168 |
169 | if (!DEP.factions.enabled()) return;
170 |
171 | if (event.getType() == Type.FACTION) {
172 | Player player = event.getCause().getPlayer();
173 |
174 | if (!CREATEVAULT_FACTION.allowed(player)) {
175 | player.sendMessage(LANG.plugin_faction_noVaultPerm);
176 |
177 | return;
178 | }
179 |
180 | AccountHolder owner;
181 |
182 | String ownername = event.getCause().getLine(2);
183 |
184 | if (ownername != null && ownername.length() > 0 && CREATEVAULT_ADMIN.allowed(player)) {
185 | // attempting to create account for named faction
186 | owner = Gringotts.getInstance().getAccountHolderFactory().get("faction", ownername);
187 |
188 | if (owner == null) {
189 | return;
190 | }
191 | } else {
192 | owner = DEP.factions.getFactionAccountHolder(player);
193 | }
194 |
195 | if (owner == null) {
196 | player.sendMessage(LANG.plugin_faction_notInFaction);
197 |
198 | return;
199 | }
200 |
201 | event.setOwner(owner);
202 | event.setValid(true);
203 | }
204 | }
205 | }
206 |
207 | class FactionAccountHolder implements AccountHolder {
208 |
209 | private static final String TAG_FACTION = "faction";
210 | private final Faction owner;
211 |
212 | /**
213 | * Default ctor.
214 | */
215 | public FactionAccountHolder(Faction owner) {
216 | this.owner = owner;
217 | }
218 |
219 | public FactionAccountHolder(String id) {
220 | Faction faction = FactionColl.get().getFixed(id);
221 |
222 | if (faction != null) {
223 | this.owner = faction;
224 | } else {
225 | throw new NullPointerException("Attempted to create account holder with null faction.");
226 | }
227 | }
228 |
229 | @Override
230 | public String getName() {
231 | return owner.getName();
232 | }
233 |
234 | @Override
235 | public void sendMessage(String message) {
236 | owner.sendMessage(message);
237 | }
238 |
239 | @Override
240 | public int hashCode() {
241 | final int prime = 31;
242 | int result = 1;
243 |
244 | result = prime * result + ((owner == null) ? 0 : owner.getId().hashCode());
245 |
246 | return result;
247 | }
248 |
249 | @Override
250 | public boolean equals(Object obj) {
251 | if (this == obj) {
252 | return true;
253 | }
254 |
255 | if (obj == null || getClass() != obj.getClass()) {
256 | return false;
257 | }
258 |
259 | FactionAccountHolder other = (FactionAccountHolder) obj;
260 |
261 | if (this.owner == null) {
262 | return other.owner == null;
263 | } else {
264 | return this.owner.getId().equals(other.owner.getId());
265 | }
266 | }
267 |
268 | @Override
269 | public String getType() {
270 | return TAG_FACTION;
271 | }
272 |
273 | @Override
274 | public String toString() {
275 | return "FactionAccountHolder(" + getName() + ")";
276 | }
277 |
278 | @Override
279 | public String getId() {
280 | return owner.getId();
281 | }
282 | }
283 |
--------------------------------------------------------------------------------
/doc/configuration.md:
--------------------------------------------------------------------------------
1 | Configuration, Permissions, Localization
2 | ========================================
3 |
4 | Configuration
5 | -------------
6 | As usual with Bukkit plugins, the configuration is in the config.yml in the plugin's directory. A config.yml with the default settings is is created after starting and stopping the server with the plugin for the first time.
7 |
8 | Please refer to the [default config.yml](https://github.com/MinecraftWars/Gringotts/blob/master/config.yml) for a complete example.
9 |
10 | **Note: Gringotts will currently not work correctly with Towny if you set the `economy.closed_economy` in the Towny configuration.**
11 |
12 | ### Language ###
13 | The `language` option allows you to set one of Gringotts' supported language for interaction messages with players. Currently supported options are `custom` (default/english) and `de` (German). See below on how to modify custom messages to your preferences.
14 |
15 | ### Currency ###
16 | Per default Gringotts uses emeralds as currency, but this can be changed to any other type of item.
17 |
18 | Example configuration section:
19 |
20 | currency:
21 | name:
22 | singular: Emerald
23 | plural: Emeralds
24 | digits: 2
25 | named-denominations: false
26 | denominations:
27 | - material: emerald
28 | value: 1
29 | - material: emerald_block
30 | value: 9
31 |
32 | This is the default configuration which uses emeralds as currency, with emeralds having value 1, and emerald blocks value 9.
33 |
34 | #### Individual settings
35 |
36 | * `name` Name of the currency to be used in messages to players. Please enter both a singular and plural version.
37 | * `digits` Decimal digits used in representation and calculation of the currency. Set this to 0 to use only whole number values.
38 | * `named-denominations` Whether to display money values in messages split up over denomination values, or as a single sum.
39 | * `denominations` A list of key-value mappings, defining the material and name of the item type to use as currency, and the value of the item.
40 |
41 |
42 | #### Denominations
43 |
44 | Denominations have the following format:
45 |
46 | denominations:
47 | - material: material name or id
48 | damage: damage value of material (optional)
49 | displayname: a custom name for the currency item (optional)
50 | lore: a list of custom item lore text lines (optional)
51 | value: a number
52 | unit-name: a name for the denomination for display in messages (optional)
53 | unit-name-plural: plural version of unit-name (optional)
54 | - (optional: more denominations)
55 | - ...
56 |
57 | * `material` can be an item id, a name from the [material list](https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/Material.html), or an item type parsable by Vault (see [Vault Items code](https://github.com/MilkBowl/VaultAPI/blob/master/src/main/java/net/milkbowl/vault/item/Items.java)), if Vault is installed.
58 | * `damage` is the modifier value or subtypoe for an item type. For example, all the dyes have the same item type, but different damage values.
59 | * `displayname` is a modified item name. Only items with this exact name, including colors, will count as the specified denomination. **This does *not* automatically rename all items of this type, you need to use a separate item renaming plugin for this.** It will, however, create currency items based on these settings.
60 | * `lore` is a list of custom lore lines that have to be present for an item to be counted as currency. Like `displayname`, it will only work if added by a third party plugin
61 | * `value` is a whole or fractional number denoting the value of a denomination. The number of fractional digits in a currency value should not exceed the number defined as `digits`.
62 | * `unit-name` sets the name for the denomination in messages to the player, such as account balance. If not set, uses the item's display name or regular name.
63 | * `unit-name-plural` is the plural version of `unit-name`. If not set, uses the singular unit name with 's' added.
64 |
65 |
66 | ##### Example denomination setup with different features used for each denomination
67 |
68 | The following setup shows how to specify a currency with Lapis Lazuli as minor denomination with a value of 0.05, Skeleton Heads with a value of 10 and Creeper Heads renamed to "Danger Coin" and additional added lore with a value of 60.
69 |
70 | denominations:
71 | - material: ink_sack
72 | damage: 4
73 | value: 0.05
74 | - material: skull_item
75 | value: 10
76 | - material: skull_item
77 | damage: 4
78 | displayname: 'Danger Coin'
79 | lore: ['Awarded for stupidity in the face of danger','Handle with care']
80 | value: 60
81 |
82 |
83 | ### Taxes ###
84 |
85 | Gringotts supports two types of taxes on transactions done via `/money pay` command: `flat` and `rate`. Flat taxes are a flat amount added to any transaction, while rate adds a percentage of the actual transaction. These can be used individually or combined.
86 |
87 | Example configuration section:
88 |
89 | transactiontax:
90 | flat: 1.0
91 | rate: 0.05
92 |
93 | This would add to every transaction 1 plus 5% of the transaction value. For instance, if you had issued the command `/money pay 200 notch` it would remove 211 emeralds from your account, and add 200 emeralds to notch's account.
94 |
95 |
96 | ### Misc ###
97 |
98 | startingbalance:
99 | player: 0
100 | faction: 0
101 | town: 0
102 | nation: 0
103 |
104 | Amount of virtual money to gift to players on first join, or accounts with other plugins upon creation. This money may be spent as usual, but will not be backed by physical currency. Enable these if you want your players/factions/etc. to start with some money that can't be lost or stolen.
105 |
106 | ---
107 |
108 | usevault:
109 | container: true
110 | enderchest: true
111 |
112 | Globally enable use of specific kinds of vault:
113 | * `container` Enable the use of container vaults: chests, dispensers and furnaces. If this is `false`, only player's inventory and/or enderchests will serve as a player "vault".
114 | * `enderchest` Enable use of enderchest as vault for players globally. The permission `gringotts.usevault.enderchest` may still be used to disable this on a per-player/world basis.
115 |
116 | ---
117 |
118 | balance:
119 | show-inventory: true
120 | show-vault: true
121 |
122 | Show or hide messages information in inventory and vault balance, in addition to total balance. Disable these if you'd like your balance messages to be less verbose.
123 |
124 |
125 | Localization and message customization
126 | --------------------------------------
127 | On first start of Gringotts, a `messages.yml` file will be written to the Gringotts plugin folder.
128 | You can freely edit the available strings and also include [text formatting (color) codes](http://minecraft.gamepedia.com/Formatting_codes).
129 |
130 | Some messages contain variables, for example `%player` for the player's name or `%value` for the amount transferred in a transaction. It is important not to change or translate these variable names. They may only be used in messages where they are already present in the original version, but it is safe to omit them from a custom message.
131 |
132 |
133 | Permissions
134 | -----------
135 |
136 | ### Vault creation
137 |
138 | gringotts.createvault:
139 | default: true
140 |
141 | Allow players to create any type of vault.
142 |
143 | ---
144 |
145 | gringotts.createvault.admin:
146 | default: op
147 |
148 | Allow players to create vaults for other players.
149 |
150 | ---
151 |
152 | gringotts.createvault.player:
153 | default: true
154 |
155 | Allow players to create vaults for their own account.
156 |
157 | ---
158 |
159 | gringotts.createvault.faction:
160 | default: true
161 |
162 | Allow players to create vaults for their faction (Factions only).
163 |
164 | ---
165 |
166 | gringotts.createvault.town:
167 | default: true
168 |
169 | Allow players to create vaults for their town (Towny only).
170 |
171 | ---
172 |
173 | gringotts.createvault.nation:
174 | default: true
175 |
176 | Allow players to create vaults for their nation (Towny only).
177 |
178 | ---
179 |
180 | gringotts.createvault.worldguard:
181 | default: true
182 |
183 | Allow players to create vaults for WorldGuard regions they are member of.
184 |
185 | ### Vault usage
186 |
187 | gringotts.usevault
188 | default: true
189 |
190 | Use player inventory and player's enderchest as vault when player is online.
191 |
192 | ---
193 |
194 | gringotts.usevault.inventory
195 | default: true
196 |
197 | Use player inventory as vault when player is online.
198 |
199 | ---
200 | gringotts.usevault.enderchest
201 | default: true
202 |
203 | ### User commands
204 |
205 | Use Gringotts commands.
206 |
207 | gringotts.command
208 | default: true
209 |
210 | ---
211 |
212 | Allow transfer command (pay)
213 |
214 | gringotts.transfer:
215 | default: true
216 |
217 | ---
218 |
219 | Allow withdrawal of money from chest storage to inventory via `/money withdraw`.
220 |
221 | gringotts.command.withdraw:
222 | default: true
223 |
224 | ---
225 |
226 | Allow deposit of money to chest storage from inventory via `/money deposit`.
227 |
228 | gringotts.command.deposit:
229 | default: true
230 |
231 | ### Admin permissions
232 |
233 | Allow players to transfer money to other accounts via `/money pay`
234 |
235 | gringotts.admin:
236 | default: op
237 |
238 | Allow use of all `/moneyadmin` commands
239 |
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/data/EBeanDAO.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts.data;
2 |
3 | import com.avaje.ebean.EbeanServer;
4 | import com.avaje.ebean.SqlQuery;
5 | import com.avaje.ebean.SqlRow;
6 | import com.avaje.ebean.SqlUpdate;
7 | import org.bukkit.Bukkit;
8 | import org.bukkit.Location;
9 | import org.bukkit.World;
10 | import org.bukkit.block.Block;
11 | import org.bukkit.block.Sign;
12 | import org.gestern.gringotts.AccountChest;
13 | import org.gestern.gringotts.Gringotts;
14 | import org.gestern.gringotts.GringottsAccount;
15 | import org.gestern.gringotts.Util;
16 | import org.gestern.gringotts.accountholder.AccountHolder;
17 |
18 | import java.util.Arrays;
19 | import java.util.LinkedList;
20 | import java.util.List;
21 | import java.util.logging.Logger;
22 |
23 | import static org.gestern.gringotts.Configuration.CONF;
24 |
25 | public class EBeanDAO implements DAO {
26 |
27 | private static EBeanDAO dao;
28 | private final EbeanServer db = Gringotts.getInstance().getDatabase();
29 | private final Logger log = Gringotts.getInstance().getLogger();
30 |
31 | public static EBeanDAO getDao() {
32 | if (dao != null) {
33 | return dao;
34 | }
35 |
36 | dao = new EBeanDAO();
37 |
38 | return dao;
39 | }
40 |
41 | /**
42 | * The classes comprising the DB model, required for the EBean DDL ("data description language").
43 | */
44 | public static List> getDatabaseClasses() {
45 | return Arrays.asList(EBeanAccount.class, EBeanAccountChest.class);
46 | }
47 |
48 | @Override
49 | public synchronized boolean storeAccountChest(AccountChest chest) {
50 | SqlUpdate storeChest = db.createSqlUpdate(
51 | "insert into gringotts_accountchest (world,x,y,z,account) " +
52 | "values (:world, :x, :y, :z, (select id from gringotts_account where owner=:owner and " +
53 | "type=:type))");
54 |
55 | Sign mark = chest.sign;
56 | storeChest.setParameter("world", mark.getWorld().getName());
57 | storeChest.setParameter("x", mark.getX());
58 | storeChest.setParameter("y", mark.getY());
59 | storeChest.setParameter("z", mark.getZ());
60 | storeChest.setParameter("owner", chest.account.owner.getId());
61 | storeChest.setParameter("type", chest.account.owner.getType());
62 |
63 | return storeChest.execute() > 0;
64 | }
65 |
66 | @Override
67 | public synchronized boolean destroyAccountChest(AccountChest chest) {
68 | Sign mark = chest.sign;
69 |
70 | return deleteAccountChest(mark.getWorld().getName(), mark.getX(), mark.getY(), mark.getZ());
71 | }
72 |
73 | @Override
74 | public synchronized boolean storeAccount(GringottsAccount account) {
75 | if (hasAccount(account.owner))
76 | return false;
77 |
78 | EBeanAccount acc = new EBeanAccount();
79 |
80 | acc.setOwner(account.owner.getId());
81 | acc.setType(account.owner.getType());
82 |
83 | // TODO this is business logic and should probably be outside of the DAO implementation.
84 | // also find a more elegant way of handling different account types
85 | double startValue = 0;
86 | String type = account.owner.getType();
87 |
88 | switch (type) {
89 | case "player":
90 | startValue = CONF.startBalancePlayer;
91 | break;
92 | case "faction":
93 | startValue = CONF.startBalanceFaction;
94 | break;
95 | case "town":
96 | startValue = CONF.startBalanceTown;
97 | break;
98 | case "nation":
99 | startValue = CONF.startBalanceNation;
100 | break;
101 | }
102 |
103 | acc.setCents(CONF.getCurrency().centValue(startValue));
104 | db.save(acc);
105 |
106 | return true;
107 | }
108 |
109 | @Override
110 | public synchronized boolean hasAccount(AccountHolder accountHolder) {
111 | int accCount = db
112 | .find(EBeanAccount.class)
113 | .where()
114 | .ieq("type", accountHolder.getType()).ieq("owner", accountHolder.getId())
115 | .findRowCount();
116 |
117 | return accCount == 1;
118 | }
119 |
120 | @Override
121 | public synchronized List getChests() {
122 | List result = db.createSqlQuery("SELECT ac.world, ac.x, ac.y, ac.z, a.type, a.owner " +
123 | "FROM gringotts_accountchest ac JOIN gringotts_account a ON ac.account = a.id ")
124 | .findList();
125 |
126 | List chests = new LinkedList<>();
127 |
128 | for (SqlRow c : result) {
129 | String worldName = c.getString("world");
130 | int x = c.getInteger("x");
131 | int y = c.getInteger("y");
132 | int z = c.getInteger("z");
133 |
134 | String type = c.getString("type");
135 | String ownerId = c.getString("owner");
136 |
137 | World world = Bukkit.getWorld(worldName);
138 | if (world == null) continue; // skip vaults in non-existing worlds
139 |
140 | Location loc = new Location(world, x, y, z);
141 | Block signBlock = loc.getBlock();
142 | if (Util.isSignBlock(signBlock)) {
143 | AccountHolder owner = Gringotts.getInstance().getAccountHolderFactory().get(type, ownerId);
144 | if (owner == null) {
145 | log.info("AccountHolder " + type + ":" + ownerId + " is not valid. Deleting associated account " +
146 | "chest at " + signBlock.getLocation());
147 | deleteAccountChest(signBlock.getWorld().getName(), signBlock.getX(), signBlock.getY(), signBlock
148 | .getZ());
149 | } else {
150 | GringottsAccount ownerAccount = new GringottsAccount(owner);
151 | Sign sign = (Sign) signBlock.getState();
152 | chests.add(new AccountChest(sign, ownerAccount));
153 | }
154 | } else {
155 | // remove accountchest from storage if it is not a valid chest
156 | deleteAccountChest(worldName, x, y, z);
157 | }
158 | }
159 |
160 | return chests;
161 | }
162 |
163 | private boolean deleteAccountChest(String world, int x, int y, int z) {
164 | SqlUpdate deleteChest = db.createSqlUpdate("delete from gringotts_accountchest " +
165 | "where world = :world and x = :x and y = :y and z = :z");
166 |
167 | deleteChest.setParameter("world", world);
168 | deleteChest.setParameter("x", x);
169 | deleteChest.setParameter("y", y);
170 | deleteChest.setParameter("z", z);
171 |
172 | return deleteChest.execute() > 0;
173 | }
174 |
175 | @Override
176 | public synchronized List getChests(GringottsAccount account) {
177 | // TODO ensure world interaction is done in sync task
178 | SqlQuery getChests = db.createSqlQuery("SELECT ac.world, ac.x, ac.y, ac.z " +
179 | "FROM gringotts_accountchest ac JOIN gringotts_account a ON ac.account = a.id " +
180 | "WHERE a.owner = :owner and a.type = :type");
181 |
182 | getChests.setParameter("owner", account.owner.getId());
183 | getChests.setParameter("type", account.owner.getType());
184 |
185 | List chests = new LinkedList<>();
186 | for (SqlRow result : getChests.findSet()) {
187 | String worldName = result.getString("world");
188 | int x = result.getInteger("x");
189 | int y = result.getInteger("y");
190 | int z = result.getInteger("z");
191 |
192 | World world = Bukkit.getWorld(worldName);
193 |
194 | if (world == null) {
195 | continue; // skip chest if it is in non-existent world
196 | }
197 |
198 | Location loc = new Location(world, x, y, z);
199 | Block signBlock = loc.getBlock();
200 |
201 | if (Util.isSignBlock(signBlock)) {
202 | Sign sign = (Sign) loc.getBlock().getState();
203 |
204 | chests.add(new AccountChest(sign, account));
205 | } else {
206 | // remove accountchest from storage if it is not a valid chest
207 | deleteAccountChest(worldName, x, y, z);
208 | }
209 | }
210 |
211 | return chests;
212 | }
213 |
214 | @Override
215 | public synchronized boolean storeCents(GringottsAccount account, long amount) {
216 | SqlUpdate up = db.createSqlUpdate("UPDATE gringotts_account SET cents = :cents " +
217 | "WHERE owner = :owner and type = :type");
218 |
219 | up.setParameter("cents", amount);
220 | up.setParameter("owner", account.owner.getId());
221 | up.setParameter("type", account.owner.getType());
222 |
223 | return up.execute() == 1;
224 | }
225 |
226 | @Override
227 | public synchronized long getCents(GringottsAccount account) {
228 | // can this NPE? (probably doesn't)
229 | return db.find(EBeanAccount.class)
230 | .where()
231 | .ieq("type", account.owner.getType())
232 | .ieq("owner", account.owner.getId())
233 | .findUnique().cents;
234 | }
235 |
236 | @Override
237 | public synchronized void deleteAccount(GringottsAccount acc) {
238 | // TODO implement deleteAccount, mayhaps?
239 | throw new RuntimeException("delete account not supported yet in EBeanDAO");
240 | }
241 |
242 | @Override
243 | public synchronized void shutdown() {
244 | // probably handled by Bukkit?
245 | }
246 | }
247 |
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/AccountChest.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts;
2 |
3 | import org.bukkit.Location;
4 | import org.bukkit.block.Block;
5 | import org.bukkit.block.Chest;
6 | import org.bukkit.block.Sign;
7 | import org.bukkit.inventory.DoubleChestInventory;
8 | import org.bukkit.inventory.Inventory;
9 | import org.bukkit.inventory.InventoryHolder;
10 | import org.gestern.gringotts.data.DAO;
11 |
12 | import java.util.logging.Logger;
13 |
14 | import static org.gestern.gringotts.Configuration.CONF;
15 |
16 | /**
17 | * Represents a storage unit for an account.
18 | *
19 | * @author jast
20 | */
21 | public class AccountChest {
22 |
23 | /**
24 | * Sign marking the chest as an account chest.
25 | */
26 | public final Sign sign;
27 | /**
28 | * Account this chest belongs to.
29 | */
30 | public final GringottsAccount account;
31 | private final Logger log = Gringotts.getInstance().getLogger();
32 | private final DAO dao = Gringotts.getInstance().getDao();
33 |
34 | /**
35 | * Create Account chest based on a sign marking its position and belonging to an account.
36 | *
37 | * @param sign the marker sign
38 | * @param account the account
39 | */
40 | public AccountChest(Sign sign, GringottsAccount account) {
41 | if (sign == null || account == null) {
42 | throw new IllegalArgumentException("null arguments to AccountChest() not allowed. args were: sign: " +
43 | sign + ", account: " + account);
44 | }
45 |
46 | this.sign = sign;
47 | this.account = account;
48 | }
49 |
50 | /**
51 | * The actual "chest" containing this account chest's stuff.
52 | *
53 | * @return InventoryHolder for this account chest
54 | */
55 | private InventoryHolder chest() {
56 | Block block = Util.chestBlock(sign);
57 |
58 | if (block == null) {
59 | return null;
60 | } else {
61 | return (InventoryHolder) block.getState();
62 | }
63 | }
64 |
65 | /**
66 | * Location of the storage block of this account chest.
67 | *
68 | * @return Location of the storage block of this account chest.
69 | */
70 | private Location chestLocation() {
71 | Block block = Util.chestBlock(sign);
72 |
73 | return block != null ? block.getLocation() : null;
74 | }
75 |
76 | /**
77 | * Get inventory of this account chest.
78 | *
79 | * @return inventory of this AccountChest, if any. otherwise null.
80 | */
81 | private Inventory inventory() {
82 | InventoryHolder chest = chest();
83 |
84 | return (chest != null) ? chest.getInventory() : null;
85 | }
86 |
87 | /**
88 | * Get account inventory of this account chest, which is based on the container inventory.
89 | *
90 | * @return account inventory of this account chest
91 | */
92 | private AccountInventory accountInventory() {
93 | Inventory inv = inventory();
94 |
95 | return inv != null ? new AccountInventory(inv) : null;
96 | }
97 |
98 | /**
99 | * Test if this chest is valid, and if not, removes it from storage.
100 | *
101 | * @return true if valid, false if not and was removed from storage.
102 | */
103 | private boolean updateValid() {
104 | if (notValid()) {
105 | log.info("Destroying orphaned vault: " + this);
106 | destroy();
107 |
108 | return false;
109 | } else {
110 | return true;
111 | }
112 | }
113 |
114 | /**
115 | * Return balance of this chest.
116 | *
117 | * @return balance of this chest
118 | */
119 | public long balance() {
120 |
121 | if (!updateValid()) {
122 | return 0;
123 | }
124 |
125 | AccountInventory inv = accountInventory();
126 | if (inv == null) {
127 | return 0;
128 | }
129 |
130 | return inv.balance();
131 | }
132 |
133 | /**
134 | * Attempts to add given amount to this chest.
135 | * If the amount is larger than available space, the space is filled and the actually
136 | * added amount returned.
137 | *
138 | * @return amount actually added
139 | */
140 | public long add(long value) {
141 |
142 | if (!updateValid()) {
143 | return 0;
144 | }
145 |
146 | AccountInventory inv = accountInventory();
147 | if (inv == null) {
148 | return 0;
149 | }
150 |
151 | return inv.add(value);
152 | }
153 |
154 | /**
155 | * Attempts to remove given amount from this chest.
156 | * If the amount is larger than available items, everything is removed and the number of
157 | * removed items returned.
158 | *
159 | * @param value amount to remove
160 | * @return amount actually removed from this chest
161 | */
162 | public long remove(long value) {
163 |
164 | if (!updateValid()) {
165 | return 0;
166 | }
167 |
168 | AccountInventory inv = accountInventory();
169 | if (inv == null) {
170 | return 0;
171 | }
172 |
173 | return inv.remove(value);
174 | }
175 |
176 | /**
177 | * Checks whether this chest is currently a valid vault.
178 | * It is considered valid when the sign block contains [vault] or [(type) vault] on the first line,
179 | * a name on the third line and has a chest associated with it.
180 | *
181 | * @return false if the chest can be considered a valid vault
182 | */
183 | @SuppressWarnings("SimplifiableIfStatement")
184 | public boolean notValid() {
185 | // is it still a sign?
186 | if (!Util.isSignBlock(sign.getBlock())) {
187 | return true;
188 | }
189 |
190 | // TODO refactor: common definition of valid vault types
191 | String[] lines = sign.getLines();
192 | String line0 = lines[0].toLowerCase();
193 |
194 | if (!line0.matches(CONF.vaultPattern)) {
195 | return true;
196 | }
197 |
198 | if (lines[2] == null || lines[2].length() == 0) {
199 | return true;
200 | }
201 |
202 | return chest() == null;
203 |
204 | }
205 |
206 | /**
207 | * Triggered on destruction of physical chest or sign.
208 | */
209 | void destroy() {
210 | dao.destroyAccountChest(this);
211 | sign.getBlock().breakNaturally();
212 | }
213 |
214 | @Override
215 | public String toString() {
216 | Location loc = sign.getLocation();
217 | return "[vault] "
218 | + loc.getBlockX() + ", "
219 | + loc.getBlockY() + ", "
220 | + loc.getBlockZ() + ", "
221 | + loc.getWorld();
222 | }
223 |
224 | /**
225 | * Connected chests that comprise the inventory of this account chest.
226 | *
227 | * @return chest blocks connected to this chest, if any
228 | */
229 | private Chest[] connectedChests() {
230 | Inventory inv = inventory();
231 | if (inv == null) {
232 | return new Chest[0];
233 | }
234 |
235 | if (inv instanceof DoubleChestInventory) {
236 | DoubleChestInventory dinv = (DoubleChestInventory) inv;
237 | Chest left = (Chest) (dinv.getLeftSide().getHolder());
238 | Chest right = (Chest) (dinv.getRightSide().getHolder());
239 |
240 | return new Chest[]{left, right};
241 | } else {
242 | InventoryHolder invHolder = inv.getHolder();
243 | if (invHolder instanceof Chest) {
244 | return new Chest[]{(Chest) (inv.getHolder())};
245 | }
246 | }
247 |
248 | return new Chest[0];
249 | }
250 |
251 |
252 | /* (non-Javadoc)
253 | * @see java.lang.Object#hashCode()
254 | */
255 | @Override
256 | public int hashCode() {
257 | final int prime = 31;
258 | int result = 1;
259 |
260 | result = prime * result + sign.getLocation().hashCode();
261 |
262 | return result;
263 | }
264 |
265 |
266 | /* (non-Javadoc)
267 | * @see java.lang.Object#equals(java.lang.Object)
268 | */
269 | @Override
270 | public boolean equals(Object obj) {
271 | if (this == obj) {
272 | return true;
273 | }
274 |
275 | if (obj == null) {
276 | return false;
277 | }
278 |
279 | if (getClass() != obj.getClass()) {
280 | return false;
281 | }
282 |
283 | AccountChest other = (AccountChest) obj;
284 |
285 | return this.sign.getLocation().equals(other.sign.getLocation());
286 | }
287 |
288 | /**
289 | * Determine whether the chest of another AccountChest would be connected to this chest.
290 | *
291 | * @param chest another AccountChest
292 | * @return whether the chest of another AccountChest would be connected to this chest
293 | */
294 | public boolean connected(AccountChest chest) {
295 |
296 | // no valid account chest anymore -> no connection
297 | if (!updateValid()) {
298 | return false;
299 | }
300 |
301 | Location myLoc = chestLocation();
302 |
303 | if (myLoc == null) {
304 | return false;
305 | }
306 |
307 | if (myLoc.equals(chest.chestLocation())) {
308 | return true;
309 | }
310 |
311 | // no double chest -> no further connection possible
312 | if (!(inventory() instanceof DoubleChestInventory)) {
313 | return false;
314 | }
315 |
316 |
317 | for (Chest c : chest.connectedChests()) {
318 | if (c.getLocation().equals(myLoc)) {
319 | return true;
320 | }
321 | }
322 |
323 | return false;
324 | }
325 |
326 | public GringottsAccount getAccount() {
327 | return account;
328 | }
329 | }
330 |
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/Language.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts;
2 |
3 | import org.bukkit.configuration.file.FileConfiguration;
4 |
5 | import static org.gestern.gringotts.Util.translateColors;
6 |
7 | /**
8 | * Deals with all the language Strings.
9 | *
10 | * In case Strings are not included in language.yml or there is no language.yml
11 | * there are default-values included here. If someone complains about a String not translating,
12 | * check for the yml-nodes in readLanguage below. If the node does not match the language file,
13 | * the default English message will be shown.
14 | *
15 | * @author Daenara (KanaYamamoto Q bukkit.org)
16 | */
17 | public enum Language {
18 | LANG;
19 | //global
20 | public String noperm;
21 | public String playerOnly;
22 | public String balance;
23 | public String vault_balance;
24 | public String inv_balance;
25 | public String invalid_account;
26 | public String reload;
27 | //pay command
28 | public String pay_success_tax;
29 | public String pay_success_sender;
30 | public String pay_success_target;
31 | public String pay_insufficientFunds;
32 | public String pay_insS_sender;
33 | public String pay_insS_target;
34 | public String pay_error;
35 | //deposit command
36 | public String deposit_success;
37 | public String deposit_error;
38 | //withdraw command
39 | public String withdraw_success;
40 | public String withdraw_error;
41 | //moneyadmin command
42 | public String moneyadmin_b;
43 | public String moneyadmin_add_sender;
44 | public String moneyadmin_add_target;
45 | public String moneyadmin_add_error;
46 | public String moneyadmin_rm_sender;
47 | public String moneyadmin_rm_target;
48 | public String moneyadmin_rm_error;
49 | //gringotts vaults
50 | public String vault_created;
51 | public String vault_error;
52 | public String vault_noVaultPerm;
53 | //towny plugin
54 | public String plugin_towny_noTownVaultPerm;
55 | public String plugin_towny_noTownResident;
56 | public String plugin_towny_noNationVaultPerm;
57 | public String plugin_towny_notInNation;
58 | //faction plugin
59 | public String plugin_faction_noVaultPerm;
60 | public String plugin_faction_notInFaction;
61 | //worldguard plugin
62 | public String plugin_worldguard_noVaultPerm;
63 | //vault plugin
64 | public String plugin_vault_insufficientFunds;
65 | public String plugin_vault_insufficientSpace;
66 | public String plugin_vault_error;
67 | public String plugin_vault_notImplemented;
68 |
69 | public void readLanguage(FileConfiguration savedLanguage) {
70 | ClosuresWouldBeCoolNow translator = new ClosuresWouldBeCoolNow(savedLanguage);
71 |
72 | //global
73 | LANG.noperm = translator.read(
74 | "noperm",
75 | "You do not have permission to transfer money.");
76 | LANG.playerOnly = translator.read(
77 | "playeronly",
78 | "This command can only be run by a player.");
79 | LANG.balance = translator.read(
80 | "balance",
81 | "Your current total balance: %balance");
82 | LANG.vault_balance = translator.read(
83 | "vault_balance",
84 | "Vault balance: %balance");
85 | LANG.inv_balance = translator.read(
86 | "inv_balance",
87 | "Inventory balance: %balance");
88 | LANG.invalid_account = translator.read(
89 | "invalidaccount",
90 | "Invalid account: %player");
91 | LANG.reload = translator.read(
92 | "reload",
93 | "Gringotts: Reloaded configuration!");
94 |
95 | //pay command
96 | LANG.pay_success_sender = translator.read(
97 | "pay.success.sender",
98 | "Sent %value to %player. ");
99 | LANG.pay_success_tax = translator.read(
100 | "pay.success.tax",
101 | "Received %value from %player.");
102 | LANG.pay_success_target = translator.read(
103 | "pay.success.target",
104 | "Transaction tax deducted from your account: %value");
105 | LANG.pay_error = translator.read(
106 | "pay.error",
107 | "Your attempt to send %value to %player failed for unknown reasons.");
108 | LANG.pay_insufficientFunds = translator.read(
109 | "pay.insufficientFunds",
110 | "Your account has insufficient balance. Current balance: %balance. Required: %value");
111 | LANG.pay_insS_sender = translator.read(
112 | "pay.insufficientSpace.sender",
113 | "%player has insufficient storage space for %value");
114 | LANG.pay_insS_target = translator.read(
115 | "pay.insufficientSpace.target",
116 | "%player tried to send %value, but you don't have enough space for that amount.");
117 |
118 | //deposit command
119 | LANG.deposit_success = translator.read(
120 | "deposit.success",
121 | "Deposited %value to your storage.");
122 | LANG.deposit_error = translator.read(
123 | "deposit.error",
124 | "Unable to deposit %value to your storage.");
125 |
126 | //withdraw command
127 | LANG.withdraw_success = translator.read(
128 | "withdraw.success",
129 | "Withdrew %value from your storage.");
130 | LANG.withdraw_error = translator.read(
131 | "withdraw.error",
132 | "Unable to withdraw %value from your storage.");
133 |
134 | //moneyadmin command
135 | LANG.moneyadmin_b = translator.read(
136 | "moneyadmin.b",
137 | "Balance of account %player: %balance");
138 | LANG.moneyadmin_add_sender = translator.read(
139 | "moneyadmin.add.sender",
140 | "Added %value to account %player");
141 | LANG.moneyadmin_add_target = translator.read(
142 | "moneyadmin.add.target",
143 | "Added to your account: %value");
144 | LANG.moneyadmin_add_error = translator.read(
145 | "moneyadmin.add.error",
146 | "Could not add %value to account %player");
147 | LANG.moneyadmin_rm_sender = translator.read(
148 | "moneyadmin.rm.sender",
149 | "Removed %value from account %player");
150 | LANG.moneyadmin_rm_target = translator.read(
151 | "moneyadmin.rm.target",
152 | "Removed from your account: %value");
153 | LANG.moneyadmin_rm_error = translator.read(
154 | "moneyadmin.rm.error",
155 | "Could not remove %value from account %player");
156 |
157 | //gringotts vaults
158 | LANG.vault_created = translator.read(
159 | "vault.created",
160 | "Created vault successfully.");
161 | LANG.vault_noVaultPerm = translator.read(
162 | "vault.noVaultPerm",
163 | "You do not have permission to create vaults here.");
164 | LANG.vault_error = translator.read(
165 | "vault.error",
166 | "Failed to create vault.");
167 |
168 | //towny plugin
169 | LANG.plugin_towny_noTownVaultPerm = translator.read(
170 | "plugins.towny.noTownPerm",
171 | "You do not have permission to create town vaults here.");
172 | LANG.plugin_towny_noTownResident = translator.read(
173 | "plugins.towny.noTownResident",
174 | "Cannot create town vault: You are not resident of a town.");
175 | LANG.plugin_towny_noNationVaultPerm = translator.read(
176 | "plugins.towny.NoNationVaultPerm",
177 | "You do not have permission to create nation vaults here.");
178 | LANG.plugin_towny_notInNation = translator.read(
179 | "plugins.towny.notInNation",
180 | "Cannot create nation vault: You do not belong to a nation.");
181 |
182 | //faction plugin
183 | LANG.plugin_faction_noVaultPerm = translator.read(
184 | "plugins.faction.noFactionVaultPerm",
185 | "You do not have permission to create a faction vault here.");
186 | LANG.plugin_faction_notInFaction = translator.read(
187 | "plugins.faction.notInFaction",
188 | "Cannot create faction vault: You are not in a faction.");
189 |
190 | //worldguard plugin
191 | LANG.plugin_worldguard_noVaultPerm = translator.read(
192 | "plugins.worldguard.noFactionVaultPerm",
193 | "You do not have permission to create a region vault here.");
194 |
195 | //vault plugin
196 | LANG.plugin_vault_insufficientFunds = translator.read(
197 | "plugins.vault.insufficientFunds",
198 | "Insufficient funds.");
199 | LANG.plugin_vault_insufficientSpace = translator.read(
200 | "plugins.vault.insufficientSpace",
201 | "Insufficient space.");
202 | LANG.plugin_vault_error = translator.read(
203 | "plugins.vault.unknownError",
204 | "Unknown failure.");
205 | LANG.plugin_vault_notImplemented = translator.read(
206 | "plugins.vault.notImplemented",
207 | "Gringotts does not support banks");
208 | }
209 |
210 | /**
211 | * Ah yes, an object just to wrap the config so I don't have to repeat it as a parameter.
212 | * <3 Java verbosity.
213 | */
214 | private static class ClosuresWouldBeCoolNow {
215 | private final FileConfiguration savedLanguage;
216 |
217 | private ClosuresWouldBeCoolNow(FileConfiguration savedLanguage) {
218 | this.savedLanguage = savedLanguage;
219 | }
220 |
221 | private String read(String path, String def) {
222 | return translateColors(savedLanguage.getString(path, def));
223 | }
224 | }
225 | }
226 |
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/dependency/TownyHandler.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts.dependency;
2 |
3 | import com.palmergames.bukkit.towny.Towny;
4 | import com.palmergames.bukkit.towny.TownyAPI;
5 | import com.palmergames.bukkit.towny.TownyUniverse;
6 | import com.palmergames.bukkit.towny.exceptions.NotRegisteredException;
7 | import com.palmergames.bukkit.towny.object.Nation;
8 | import com.palmergames.bukkit.towny.object.Resident;
9 | import com.palmergames.bukkit.towny.object.ResidentList;
10 | import com.palmergames.bukkit.towny.object.Town;
11 | import com.palmergames.bukkit.towny.object.economy.BankAccount;
12 | import org.bukkit.Bukkit;
13 | import org.bukkit.entity.Player;
14 | import org.bukkit.event.EventHandler;
15 | import org.bukkit.event.Listener;
16 | import org.bukkit.plugin.Plugin;
17 | import org.gestern.gringotts.Gringotts;
18 | import org.gestern.gringotts.accountholder.AccountHolder;
19 | import org.gestern.gringotts.accountholder.AccountHolderProvider;
20 | import org.gestern.gringotts.event.PlayerVaultCreationEvent;
21 |
22 | import static org.gestern.gringotts.Language.LANG;
23 | import static org.gestern.gringotts.Permissions.CREATEVAULT_ADMIN;
24 | import static org.gestern.gringotts.Permissions.CREATEVAULT_NATION;
25 | import static org.gestern.gringotts.Permissions.CREATEVAULT_TOWN;
26 | import static org.gestern.gringotts.dependency.Dependency.DEP;
27 | import static org.gestern.gringotts.event.VaultCreationEvent.Type;
28 |
29 | public abstract class TownyHandler implements DependencyHandler {
30 | /**
31 | * Get a valid towny handler if the plugin instance is valid. Otherwise get a fake one.
32 | * Apparently Towny needs this special treatment, or it will throw exceptions with unavailable classes.
33 | *
34 | * @param towny Towny plugin instance
35 | * @return a Towny handler
36 | */
37 | public static TownyHandler getTownyHandler(Plugin towny) {
38 | if (towny instanceof Towny) {
39 | return new ValidTownyHandler((Towny) towny);
40 | } else {
41 | Gringotts.getInstance().getLogger().warning("Unable to load Towny handler. Towny support will not work");
42 | return new InvalidTownyHandler();
43 | }
44 | }
45 |
46 | public abstract TownyAccountHolder getTownAccountHolder(Player player);
47 |
48 | public abstract TownyAccountHolder getNationAccountHolder(Player player);
49 |
50 | public abstract TownyAccountHolder getAccountHolderByAccountName(String name);
51 | }
52 |
53 | /**
54 | * Dummy implementation of towny handler, if the plugin cannot be loaded.
55 | *
56 | * @author jast
57 | */
58 | class InvalidTownyHandler extends TownyHandler {
59 |
60 | @Override
61 | public boolean enabled() {
62 | return false;
63 | }
64 |
65 | @Override
66 | public boolean exists() {
67 | return false;
68 | }
69 |
70 | @Override
71 | public TownyAccountHolder getTownAccountHolder(Player player) {
72 | return null;
73 | }
74 |
75 | @Override
76 | public TownyAccountHolder getNationAccountHolder(Player player) {
77 | return null;
78 | }
79 |
80 | @Override
81 | public TownyAccountHolder getAccountHolderByAccountName(String name) {
82 | return null;
83 | }
84 |
85 | }
86 |
87 | class ValidTownyHandler extends TownyHandler implements AccountHolderProvider {
88 |
89 | private static final String TAG_TOWN = "town";
90 | private static final String TAG_NATION = "nation";
91 | private final Towny plugin;
92 |
93 | public ValidTownyHandler(Towny plugin) {
94 | this.plugin = plugin;
95 | Bukkit.getPluginManager().registerEvents(new TownyListener(), Gringotts.getInstance());
96 | Gringotts.getInstance().registerAccountHolderProvider(TAG_TOWN, this);
97 | Gringotts.getInstance().registerAccountHolderProvider(TAG_NATION, this);
98 | }
99 |
100 | /**
101 | * Get a TownyAccountHolder for the town of which player is a resident, if any.
102 | *
103 | * @param player player to get town for
104 | * @return TownyAccountHolder for the town of which player is a resident, if any. null otherwise.
105 | */
106 | @Override
107 | public TownyAccountHolder getTownAccountHolder(Player player) {
108 | try {
109 | Resident resident = TownyUniverse.getInstance().getResident(player.getUniqueId());
110 | Town town = resident.getTown();
111 |
112 | return new TownyAccountHolder(town.getAccount(), TAG_TOWN);
113 | } catch (NotRegisteredException ignored) {
114 | }
115 |
116 | return null;
117 | }
118 |
119 | /**
120 | * Get a TownyAccountHolder for the nation of which player is a resident, if any.
121 | *
122 | * @param player player to get nation for
123 | * @return TownyAccountHolder for the nation of which player is a resident, if any. null otherwise.
124 | */
125 | @Override
126 | public TownyAccountHolder getNationAccountHolder(Player player) {
127 | try {
128 | Resident resident = TownyUniverse.getInstance().getResident(player.getUniqueId());
129 | Town town = resident.getTown();
130 | Nation nation = town.getNation();
131 |
132 | return new TownyAccountHolder(nation.getAccount(), TAG_NATION);
133 | } catch (NotRegisteredException ignored) {
134 | }
135 |
136 | return null;
137 | }
138 |
139 | /**
140 | * Get a TownyAccountHolder based on the name of the account.
141 | * Names beginning with "town-" will beget a town account holder and names beginning with "nation-"
142 | * a nation account holder.
143 | *
144 | * @param name Name of the account.
145 | * @return a TownyAccountHolder based on the name of the account
146 | */
147 | @Override
148 | public TownyAccountHolder getAccountHolderByAccountName(String name) {
149 |
150 | if (name.startsWith("town-")) {
151 | try {
152 | Town teo = TownyAPI.getInstance().getDataSource().getTown(name.substring(5));
153 |
154 | return new TownyAccountHolder(teo.getAccount(), TAG_TOWN);
155 | } catch (NotRegisteredException ignored) {
156 | }
157 | }
158 |
159 | if (name.startsWith("nation-")) {
160 | try {
161 | Nation teo = TownyAPI.getInstance().getDataSource().getNation(name.substring(7));
162 |
163 | return new TownyAccountHolder(teo.getAccount(), TAG_NATION);
164 | } catch (NotRegisteredException ignored) {
165 | }
166 | }
167 |
168 | return null;
169 | }
170 |
171 |
172 | @Override
173 | public boolean enabled() {
174 | return plugin != null;
175 | }
176 |
177 | @Override
178 | public boolean exists() {
179 | return plugin != null;
180 | }
181 |
182 | @Override
183 | public TownyAccountHolder getAccountHolder(String id) {
184 | return getAccountHolderByAccountName(id);
185 | }
186 | }
187 |
188 | class TownyListener implements Listener {
189 |
190 | @EventHandler
191 | public void vaultCreated(PlayerVaultCreationEvent event) {
192 | // some listener already claimed this event
193 | if (event.isValid()) return;
194 |
195 | if (!DEP.towny.enabled()) return;
196 |
197 | String ownername = event.getCause().getLine(2);
198 | Player player = event.getCause().getPlayer();
199 | boolean forOther = ownername != null && ownername.length() > 0 && CREATEVAULT_ADMIN.allowed(player);
200 |
201 | AccountHolder owner;
202 | if (event.getType() == Type.TOWN) {
203 | if (!CREATEVAULT_TOWN.allowed(player)) {
204 | player.sendMessage(LANG.plugin_towny_noTownVaultPerm);
205 |
206 | return;
207 | }
208 |
209 | if (forOther) {
210 | owner = DEP.towny.getAccountHolderByAccountName("town-" + ownername);
211 |
212 | if (owner == null) {
213 | return;
214 | }
215 | } else {
216 | owner = DEP.towny.getTownAccountHolder(player);
217 | }
218 |
219 | if (owner == null) {
220 | player.sendMessage(LANG.plugin_towny_noTownResident);
221 |
222 | return;
223 | }
224 |
225 | event.setOwner(owner);
226 | event.setValid(true);
227 |
228 | } else if (event.getType() == Type.NATION) {
229 | if (!CREATEVAULT_NATION.allowed(player)) {
230 | player.sendMessage(LANG.plugin_towny_noNationVaultPerm);
231 |
232 | return;
233 | }
234 |
235 | if (forOther) {
236 | owner = DEP.towny.getAccountHolderByAccountName("nation-" + ownername);
237 |
238 | if (owner == null) {
239 | return;
240 | }
241 | } else {
242 | owner = DEP.towny.getNationAccountHolder(player);
243 | }
244 |
245 | if (owner == null) {
246 | player.sendMessage(LANG.plugin_towny_notInNation);
247 |
248 | return;
249 | }
250 |
251 | event.setOwner(owner);
252 | event.setValid(true);
253 | }
254 | }
255 | }
256 |
257 | class TownyAccountHolder implements AccountHolder {
258 |
259 | public final BankAccount owner;
260 | public final String type;
261 |
262 | public TownyAccountHolder(BankAccount owner, String type) {
263 | this.owner = owner;
264 | this.type = type;
265 | }
266 |
267 | @Override
268 | public String getName() {
269 | return owner.getName();
270 | }
271 |
272 | /**
273 | * Send a message to all online players of the specified {@link Town} or {@link Nation}
274 | *
275 | * @param message to send
276 | */
277 | @Override
278 | public void sendMessage(String message) {
279 | if (owner instanceof ResidentList) {
280 | TownyAPI.getInstance().getOnlinePlayers((ResidentList) owner)
281 | .forEach(player -> player.sendMessage(message));
282 | }
283 | }
284 |
285 | @Override
286 | public String getType() {
287 | return type;
288 | }
289 |
290 | @Override
291 | public String getId() {
292 | return owner.getName();
293 | }
294 |
295 | @Override
296 | public String toString() {
297 | return "TownyAccountHolder(" + owner.getName() + ")";
298 | }
299 |
300 | }
301 |
302 |
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/GringottsAccount.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts;
2 |
3 | import org.bukkit.Bukkit;
4 | import org.bukkit.OfflinePlayer;
5 | import org.bukkit.entity.Player;
6 | import org.gestern.gringotts.accountholder.AccountHolder;
7 | import org.gestern.gringotts.accountholder.PlayerAccountHolder;
8 | import org.gestern.gringotts.api.TransactionResult;
9 | import org.gestern.gringotts.currency.Denomination;
10 | import org.gestern.gringotts.data.DAO;
11 |
12 | import java.util.List;
13 | import java.util.Optional;
14 | import java.util.concurrent.*;
15 | import java.util.logging.Logger;
16 |
17 | import static org.gestern.gringotts.Configuration.CONF;
18 | import static org.gestern.gringotts.Permissions.USEVAULT_ENDERCHEST;
19 | import static org.gestern.gringotts.Permissions.USEVAULT_INVENTORY;
20 | import static org.gestern.gringotts.api.TransactionResult.*;
21 |
22 | /**
23 | * Implementation of inventory-based accounts with a virtual overflow capacity.
24 | * Has support for player accounts specifically and works with any other container storage.
25 | *
26 | * @author jast
27 | */
28 | public class GringottsAccount {
29 |
30 | public final AccountHolder owner;
31 | @SuppressWarnings("unused")
32 | private final Logger log = Gringotts.getInstance().getLogger();
33 | private final DAO dao = Gringotts.getInstance().getDao();
34 |
35 | public GringottsAccount(AccountHolder owner) {
36 | if (owner == null) {
37 | throw new IllegalArgumentException("Account owner cannot be null");
38 | }
39 | this.owner = owner;
40 | }
41 |
42 | /**
43 | * Call a function in the main thread. The returned CompletionStage will be completed after the function is called.
44 | *
45 | * @param callMe function to call
46 | * @return will be completed after function is called
47 | */
48 | private static CompletableFuture callSync(Callable callMe) {
49 | final CompletableFuture f = new CompletableFuture<>();
50 |
51 | Runnable runMe = () -> {
52 | try {
53 | f.complete(callMe.call());
54 | } catch (Exception e) {
55 | f.completeExceptionally(e);
56 | }
57 | };
58 |
59 | if (Bukkit.isPrimaryThread()) {
60 | runMe.run();
61 | } else {
62 | Bukkit.getScheduler().scheduleSyncDelayedTask(Gringotts.getInstance(), runMe);
63 | }
64 |
65 | return f;
66 | }
67 |
68 | /**
69 | * Current balance of this account in cents
70 | *
71 | * @return current balance of this account in cents
72 | */
73 | public long getBalance() {
74 |
75 | CompletableFuture cents = getCents();
76 | CompletableFuture playerInv = countPlayerInventory();
77 | CompletableFuture chestInv = countChestInventories();
78 |
79 | // order of combination is important, because chestInv/playerInv might have to run on main thread
80 | CompletableFuture f = chestInv
81 | .thenCombine(playerInv, (c, p) -> c + p)
82 | .thenCombine(cents, (b, c) -> b + c);
83 |
84 | return getTimeout(f);
85 | }
86 |
87 | /**
88 | * Current balance this account has in chest(s) in cents
89 | *
90 | * @return current balance this account has in chest(s) in cents
91 | */
92 | public long getVaultBalance() {
93 | return getTimeout(countChestInventories());
94 | }
95 |
96 | /**
97 | * Current balance this account has in inventory in cents
98 | *
99 | * @return current balance this account has in inventory in cents
100 | */
101 | public long getInvBalance() {
102 | CompletableFuture cents = getCents();
103 | CompletableFuture playerInv = countPlayerInventory();
104 | CompletableFuture f = cents.thenCombine(playerInv, (p, c) -> p + c);
105 |
106 | return getTimeout(f);
107 | }
108 |
109 | /**
110 | * Add an amount in cents to this account if able to.
111 | *
112 | * @param amount amount in cents to add
113 | * @return Whether amount successfully added
114 | */
115 | public TransactionResult add(long amount) {
116 |
117 | Callable callMe = () -> {
118 |
119 | // Cannot add negative amount
120 | if (amount < 0) {
121 | return ERROR;
122 | }
123 |
124 | long centsStored = dao.getCents(this);
125 |
126 | long remaining = amount + centsStored;
127 |
128 | // add currency to account's vaults
129 | if (CONF.usevaultContainer) {
130 | for (AccountChest chest : dao.getChests(this)) {
131 | remaining -= chest.add(remaining);
132 | if (remaining <= 0) break;
133 | }
134 | }
135 |
136 | // add stuff to player's inventory and enderchest too, when they are online
137 | Optional playerOpt = playerOwner();
138 | if (playerOpt.isPresent()) {
139 | Player player = playerOpt.get();
140 | if (USEVAULT_INVENTORY.allowed(player)) {
141 | remaining -= new AccountInventory(player.getInventory()).add(remaining);
142 | }
143 | if (CONF.usevaultEnderchest && USEVAULT_ENDERCHEST.allowed(player)) {
144 | remaining -= new AccountInventory(player.getEnderChest()).add(remaining);
145 | }
146 | }
147 |
148 | // allow smallest denom value as threshold for available space
149 | // TODO make maximum virtual amount configurable
150 | // this is under the assumption that there is always at least 1 denomination
151 | List denoms = CONF.getCurrency().getDenominations();
152 | long smallestDenomValue = denoms.get(denoms.size() - 1).getValue();
153 | if (remaining < smallestDenomValue) {
154 | dao.storeCents(this, remaining);
155 | remaining = 0;
156 | }
157 |
158 | if (remaining == 0) {
159 | return SUCCESS;
160 | } else {
161 | // failed, remove the stuff added so far
162 | remove(amount - remaining);
163 | return INSUFFICIENT_SPACE;
164 | }
165 | };
166 |
167 | return getTimeout(callSync(callMe));
168 | }
169 |
170 | /**
171 | * Attempt to remove an amount in cents from this account.
172 | * If the account contains less than the specified amount, returns false
173 | *
174 | * @param amount amount in cents to remove
175 | * @return amount actually removed.
176 | */
177 | public TransactionResult remove(long amount) {
178 |
179 | Callable callMe = () -> {
180 | // Cannot remove negative amount
181 | if (amount < 0) {
182 | return ERROR;
183 | }
184 |
185 | // Make sure we have enough to remove
186 | if (getBalance() < amount) {
187 | return INSUFFICIENT_FUNDS;
188 | }
189 |
190 | long remaining = amount;
191 |
192 | // Now remove the physical amount left
193 | if (CONF.usevaultContainer) {
194 | for (AccountChest chest : dao.getChests(this)) {
195 | remaining -= chest.remove(remaining);
196 | }
197 | }
198 |
199 | Optional playerOpt = playerOwner();
200 | if (playerOpt.isPresent()) {
201 | Player player = playerOpt.get();
202 | if (USEVAULT_INVENTORY.allowed(player)) {
203 | remaining -= new AccountInventory(player.getInventory()).remove(remaining);
204 | }
205 | if (CONF.usevaultEnderchest && USEVAULT_ENDERCHEST.allowed(player)) {
206 | remaining -= new AccountInventory(player.getEnderChest()).remove(remaining);
207 | }
208 | }
209 |
210 | if (remaining < 0)
211 | // took too much, pay back the extra
212 | return add(-remaining);
213 |
214 | if (remaining > 0) {
215 | // cannot represent the leftover in our denominations, take them from the virtual reserve
216 | long cents = dao.getCents(this);
217 | dao.storeCents(this, cents - remaining);
218 | }
219 |
220 | return SUCCESS;
221 | };
222 |
223 | return getTimeout(callSync(callMe));
224 | }
225 |
226 | @Override
227 | public String toString() {
228 | return "Account (" + owner + ")";
229 | }
230 |
231 | /**
232 | * Returns the player owning this account, if the owner is actually a player and online.
233 | *
234 | * @return {@link Optional} of the player owning this account, if the owner is actually a player and online, otherwise
235 | * empty.
236 | */
237 | private Optional playerOwner() {
238 | if (owner instanceof PlayerAccountHolder) {
239 | OfflinePlayer player = ((PlayerAccountHolder) owner).accountHolder;
240 | return Optional.ofNullable(player.getPlayer());
241 | }
242 |
243 | return Optional.empty();
244 | }
245 |
246 | private CompletableFuture countChestInventories() {
247 |
248 | Callable callMe = () -> {
249 | List chests = dao.getChests(this);
250 | long balance = 0;
251 | if (CONF.usevaultContainer) {
252 | for (AccountChest chest : chests) {
253 | balance += chest.balance();
254 | }
255 | }
256 |
257 | Optional playerOpt = playerOwner();
258 | if (playerOpt.isPresent()) {
259 | Player player = playerOpt.get();
260 | if (CONF.usevaultEnderchest && USEVAULT_ENDERCHEST.allowed(player)) {
261 | balance += new AccountInventory(player.getEnderChest()).balance();
262 | }
263 | }
264 | return balance;
265 | };
266 |
267 | return callSync(callMe);
268 | }
269 |
270 | private CompletableFuture countPlayerInventory() {
271 |
272 | Callable callMe = () -> {
273 | long balance = 0;
274 |
275 | Optional playerOpt = playerOwner();
276 | if (playerOpt.isPresent() && USEVAULT_INVENTORY.allowed(playerOpt.get())) {
277 | Player player = playerOpt.get();
278 | balance += new AccountInventory(player.getInventory()).balance();
279 | }
280 | return balance;
281 | };
282 |
283 | return callSync(callMe);
284 | }
285 |
286 | private CompletableFuture getCents() {
287 | return CompletableFuture.supplyAsync(() -> dao.getCents(this));
288 | }
289 |
290 | private V getTimeout(CompletableFuture f) {
291 | try {
292 | return f.get(1, TimeUnit.SECONDS);
293 | } catch (InterruptedException | ExecutionException | TimeoutException e) {
294 | throw new GringottsException(e);
295 | }
296 | }
297 |
298 | }
299 |
--------------------------------------------------------------------------------
/src/main/java/org/gestern/gringotts/api/impl/GringottsEco.java:
--------------------------------------------------------------------------------
1 | package org.gestern.gringotts.api.impl;
2 |
3 | import org.bukkit.Bukkit;
4 | import org.bukkit.entity.Player;
5 | import org.gestern.gringotts.AccountInventory;
6 | import org.gestern.gringotts.Gringotts;
7 | import org.gestern.gringotts.GringottsAccount;
8 | import org.gestern.gringotts.accountholder.AccountHolder;
9 | import org.gestern.gringotts.accountholder.AccountHolderFactory;
10 | import org.gestern.gringotts.accountholder.PlayerAccountHolder;
11 | import org.gestern.gringotts.api.*;
12 | import org.gestern.gringotts.currency.GringottsCurrency;
13 | import org.gestern.gringotts.data.DAO;
14 |
15 | import java.util.Collections;
16 | import java.util.Set;
17 | import java.util.UUID;
18 |
19 | import static org.gestern.gringotts.Configuration.CONF;
20 | import static org.gestern.gringotts.api.TransactionResult.ERROR;
21 | import static org.gestern.gringotts.api.TransactionResult.SUCCESS;
22 |
23 | public class GringottsEco implements Eco {
24 |
25 | private static final String TAG_PLAYER = "player";
26 | private final AccountHolderFactory accountOwners = Gringotts.getInstance().getAccountHolderFactory();
27 | private final DAO dao = Gringotts.getInstance().getDao();
28 |
29 | @Override
30 | public Account account(String id) {
31 | AccountHolder owner = accountOwners.get(id);
32 |
33 | if (owner == null) {
34 | return new InvalidAccount("virtual", id);
35 | }
36 |
37 | GringottsAccount gAccount = Gringotts.getInstance().getAccounting().getAccount(owner);
38 |
39 | return new ValidAccount(gAccount);
40 |
41 | }
42 |
43 | @Override
44 | public PlayerAccount player(String name) {
45 | AccountHolder owner = accountOwners.get(TAG_PLAYER, name);
46 |
47 | if (owner instanceof PlayerAccountHolder) {
48 | return new ValidPlayerAccount(Gringotts.getInstance().getAccounting().getAccount(owner));
49 | }
50 |
51 | return new InvalidAccount(TAG_PLAYER, name);
52 | }
53 |
54 | @Override
55 | public PlayerAccount player(UUID id) {
56 | AccountHolder owner = accountOwners.get(TAG_PLAYER, id.toString());
57 |
58 | if (owner instanceof PlayerAccountHolder) {
59 | return new ValidPlayerAccount(Gringotts.getInstance().getAccounting().getAccount(owner));
60 | }
61 |
62 | return new InvalidAccount(TAG_PLAYER, id.toString());
63 | }
64 |
65 | @Override
66 | public BankAccount bank(String name) {
67 | return new InvalidAccount("bank", name);
68 | }
69 |
70 | @Override
71 | public Account custom(String type, String id) {
72 | AccountHolder owner = accountOwners.get(type, id);
73 |
74 | if (owner == null) {
75 | return new InvalidAccount(type, id);
76 | }
77 |
78 | GringottsAccount acc = new GringottsAccount(owner);
79 |
80 | return new ValidAccount(acc);
81 | }
82 |
83 | @Override
84 | public Account faction(String id) {
85 | return custom("faction", id);
86 | }
87 |
88 | @Override
89 | public Account town(String id) {
90 | return custom("town", id);
91 |
92 | }
93 |
94 | @Override
95 | public Account nation(String id) {
96 | return custom("nation", id);
97 | }
98 |
99 | @Override
100 | public Currency currency() {
101 | return new Curr(CONF.getCurrency());
102 | }
103 |
104 | @Override
105 | public boolean supportsBanks() {
106 | return true;
107 | }
108 |
109 | @Override
110 | public Set getBanks() {
111 | // TODO implement banks
112 | return Collections.emptySet();
113 | }
114 |
115 | private class InvalidAccount implements Account, BankAccount, PlayerAccount {
116 |
117 | private final String type;
118 | private final String id;
119 |
120 | InvalidAccount(String type, String id) {
121 | this.type = type;
122 | this.id = id;
123 | }
124 |
125 | @Override
126 | public boolean exists() {
127 | return false;
128 | }
129 |
130 | @Override
131 | public Account create() {
132 | // TODO if account type allows virtual accounts, create it
133 | return this;
134 | }
135 |
136 | @Override
137 | public Account delete() {
138 | return this; // delete invalid account is still invalid
139 | }
140 |
141 | @Override
142 | public double balance() {
143 | return 0; // invalid account has 0 balance
144 | }
145 |
146 | @Override
147 | public double vaultBalance() { return 0; }
148 |
149 | @Override
150 | public double invBalance() { return 0; }
151 |
152 | @Override
153 | public boolean has(double value) {
154 | return false; // invalid account has nothing
155 | }
156 |
157 | @Override
158 | public TransactionResult setBalance(double newBalance) {
159 | return ERROR;
160 | }
161 |
162 | @Override
163 | public TransactionResult add(double value) {
164 | return ERROR;
165 | }
166 |
167 | @Override
168 | public TransactionResult remove(double value) {
169 | return ERROR;
170 | }
171 |
172 | @Override
173 | public Transaction send(double value) {
174 | return new GringottsTransaction(this, value);
175 | }
176 |
177 | @Override
178 | public String type() {
179 | return type;
180 | }
181 |
182 | @Override
183 | public String id() {
184 | return id;
185 | }
186 |
187 | @Override
188 | public void message(String message) {
189 | // do nothing - no owner on this
190 | }
191 |
192 | @Override
193 | public BankAccount addOwner(String player) {
194 | return this;
195 | }
196 |
197 | @Override
198 | public BankAccount addMember(String player) {
199 | return this;
200 | }
201 |
202 | @Override
203 | public boolean isOwner(String player) {
204 | return false;
205 | }
206 |
207 | @Override
208 | public boolean isMember(String player) {
209 | return false;
210 | }
211 |
212 | @Override
213 | public TransactionResult deposit(double value) {
214 | return ERROR;
215 | }
216 |
217 | @Override
218 | public TransactionResult withdraw(double value) {
219 | return ERROR;
220 | }
221 |
222 | }
223 |
224 | private class ValidAccount implements Account {
225 |
226 | protected final GringottsAccount acc;
227 |
228 | public ValidAccount(GringottsAccount acc) {
229 | this.acc = acc;
230 | }
231 |
232 | @Override
233 | public boolean exists() {
234 | // since this is a valid account, returns true
235 | return true;
236 | }
237 |
238 | @Override
239 | public Account create() {
240 | return this;
241 | }
242 |
243 | @Override
244 | public Account delete() {
245 | dao.deleteAccount(acc);
246 | throw new RuntimeException("deleting accounts not supported by Gringotts");
247 | }
248 |
249 | @Override
250 | public double balance() {
251 | return CONF.getCurrency().displayValue(acc.getBalance());
252 | }
253 |
254 | @Override
255 | public double vaultBalance() {
256 | return CONF.getCurrency().displayValue(acc.getVaultBalance());
257 | }
258 |
259 | @Override
260 | public double invBalance() {
261 | return CONF.getCurrency().displayValue(acc.getInvBalance());
262 | }
263 |
264 | @Override
265 | public boolean has(double value) {
266 | return acc.getBalance() >= CONF.getCurrency().centValue(value);
267 | }
268 |
269 | @Override
270 | public TransactionResult setBalance(double newBalance) {
271 | return add(balance() - newBalance);
272 | }
273 |
274 | @Override
275 | public TransactionResult add(double value) {
276 | if (value < 0) {
277 | return remove(-value);
278 | }
279 |
280 | return acc.add(CONF.getCurrency().centValue(value));
281 | }
282 |
283 | @Override
284 | public TransactionResult remove(double value) {
285 | if (value < 0) {
286 | return add(-value);
287 | }
288 |
289 | return acc.remove(CONF.getCurrency().centValue(value));
290 | }
291 |
292 | @Override
293 | public Transaction send(double value) {
294 | return new GringottsTransaction(this, value);
295 | }
296 |
297 | @Override
298 | public String type() {
299 | return acc.owner.getType();
300 | }
301 |
302 | @Override
303 | public String id() {
304 | return acc.owner.getId();
305 | }
306 |
307 | @Override
308 | public void message(String message) {
309 | acc.owner.sendMessage(message);
310 | }
311 | }
312 |
313 | private class ValidPlayerAccount extends ValidAccount implements PlayerAccount {
314 |
315 | public ValidPlayerAccount(GringottsAccount acc) {
316 | super(acc);
317 | }
318 |
319 | @Override
320 | public TransactionResult deposit(double value) {
321 | PlayerAccountHolder owner = (PlayerAccountHolder) acc.owner;
322 | Player player = Bukkit.getPlayer(owner.getUUID());
323 | AccountInventory playerInventory = new AccountInventory(player.getInventory());
324 | long centValue = CONF.getCurrency().centValue(value);
325 | long toDeposit = playerInventory.remove(centValue);
326 |
327 | if (toDeposit > centValue) {
328 | toDeposit -= playerInventory.add(toDeposit - centValue);
329 | }
330 |
331 | TransactionResult result = player(player.getUniqueId()).add(CONF.getCurrency().displayValue(toDeposit));
332 |
333 | if (result != SUCCESS) {
334 | playerInventory.add(toDeposit);
335 | }
336 |
337 | return result;
338 | }
339 |
340 | @Override
341 | public TransactionResult withdraw(double value) {
342 | PlayerAccountHolder owner = (PlayerAccountHolder) acc.owner;
343 | Player player = Bukkit.getPlayer(owner.getUUID());
344 | AccountInventory playerInventory = new AccountInventory(player.getInventory());
345 | long centValue = CONF.getCurrency().centValue(value);
346 | TransactionResult remove = acc.remove(centValue);
347 |
348 | if (remove == SUCCESS) {
349 | long withdrawn = playerInventory.add(centValue);
350 | return acc.add(centValue - withdrawn); // add possible leftovers back to account
351 | }
352 |
353 | return remove;
354 | }
355 |
356 | }
357 |
358 | private class Curr implements Currency {
359 |
360 | final GringottsCurrency gcurr;
361 | final String formatString; // TODO this should be configurable
362 |
363 | Curr(GringottsCurrency curr) {
364 | this.gcurr = curr;
365 | formatString = "%." + curr.getDigits() + "f %s";
366 | }
367 |
368 | @Override
369 | public String name() {
370 | return gcurr.getName();
371 | }
372 |
373 | @Override
374 | public String namePlural() {
375 | return gcurr.getNamePlural();
376 | }
377 |
378 | @Override
379 | public String format(double value) {
380 | return CONF.getCurrency().format(formatString, value);
381 | }
382 |
383 | @Override
384 | public int fractionalDigits() {
385 | return gcurr.getDigits();
386 | }
387 |
388 | }
389 | }
390 |
--------------------------------------------------------------------------------