├── _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 | [![Join the chat at https://gitter.im/MinecraftWars/Gringotts](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/MinecraftWars/Gringotts?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 5 | [![Build Status](https://travis-ci.org/MinecraftWars/Gringotts.svg?branch=master)](https://travis-ci.org/MinecraftWars/Gringotts) 6 | [![](https://jitpack.io/v/MinecraftWars/Gringotts.svg)](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 | --------------------------------------------------------------------------------