/*
* Copyright 2011 Tyler Blair. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and contributors and should not be interpreted as representing official policies,
* either expressed or implied, of anybody else.
*/
package com.griefcraft.lwc;
import com.griefcraft.bukkit.LWCEconomyPlugin;
import com.griefcraft.integration.ICurrency;
import com.griefcraft.model.History;
import com.griefcraft.model.LWCPlayer;
import com.griefcraft.model.Protection;
import com.griefcraft.scripting.JavaModule;
import com.griefcraft.scripting.event.LWCProtectionDestroyEvent;
import com.griefcraft.scripting.event.LWCProtectionInteractEvent;
import com.griefcraft.scripting.event.LWCProtectionRegisterEvent;
import com.griefcraft.scripting.event.LWCProtectionRegistrationPostEvent;
import com.griefcraft.scripting.event.LWCProtectionRemovePostEvent;
import com.griefcraft.util.Colors;
import com.griefcraft.util.config.Configuration;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.event.block.Action;
import org.bukkit.inventory.InventoryHolder;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
public class EconomyModule extends JavaModule {
public enum DiscountType {
/**
* The discount will apply while the player has under X protections total.
*/
TOTAL,
/**
* The discount will only give the player X discounted protections - no more.
*/
EXACT,
/**
* The discount will apply while the player has under X DISCOUNTED protections total.
* This is different from TOTAL because if they have 5 free protections, they get 5
* for free, and if they remove one of the free ones, they will get 1 protection
* for free.
*/
IN_USE
}
private Logger logger = Logger.getLogger("LWC");
/**
* The iConomy module configuration
*/
private Configuration configuration = Configuration.load("iconomy.yml");
/**
* The bukkit plugin
*/
private LWCEconomyPlugin plugin;
/**
* The discount type to use
*/
private DiscountType discountType;
/**
* A cache of prices. When a value is inputted, it stays in memory for milliseconds at best.
* The best way to do this? ha, probably not
*/
private Map<Location, String> priceCache = Collections.synchronizedMap(new HashMap<Location, String>());
/**
* Our cache
*/
private final Map<String, String> cache = new HashMap<String, String>();
public EconomyModule(LWCEconomyPlugin plugin) {
this.plugin = plugin;
}
@Override
public void onProtectionInteract(LWCProtectionInteractEvent event) {
if (event.getResult() != Result.DEFAULT || !event.canAccess()) {
return;
}
if (!configuration.getBoolean("iConomy.enabled", true)) {
return;
}
LWC lwc = event.getLWC();
Protection protection = event.getProtection();
Player player = event.getPlayer();
// first, do we still have a currency processor?
if (!lwc.getCurrency().isActive()) {
return;
}
// is it actually a container? :p
if (!(protection.getBlock().getState() instanceof InventoryHolder)) {
return;
}
// Are they right clicking the chest (aka open) ?
if (event.getEvent().getAction() != Action.RIGHT_CLICK_BLOCK) {
return;
}
// Usage fee
double usageFee = resolveDouble(player, "usageFee", false);
// No fee! :D
if (usageFee <= 0) {
return;
}
// Can they afford it?
if (!lwc.getCurrency().canAfford(player, usageFee)) {
// Nope!
player.sendMessage(Colors.Red + "You need " + lwc.getCurrency().format(usageFee) + " to open your protection!");
event.setResult(Result.CANCEL);
return;
}
// Charge them!
lwc.getCurrency().removeMoney(player, usageFee);
player.sendMessage(Colors.Green + "You have been charged " + lwc.getCurrency().format(usageFee) + " to open your protection.");
}
@Override
public void onDestroyProtection(LWCProtectionDestroyEvent event) {
if (event.isCancelled()) {
return;
}
if (!configuration.getBoolean("iConomy.enabled", true)) {
return;
}
// is refunding enabled?
if (!configuration.getBoolean("iConomy.refunds", true)) {
return;
}
if (!LWC.getInstance().isHistoryEnabled()) {
return;
}
LWC lwc = event.getLWC();
Protection protection = event.getProtection();
Player player = event.getPlayer();
// first, do we still have a currency processor?
if (!lwc.getCurrency().isActive()) {
return;
}
// Does it support the server bank feature
if (!lwc.getCurrency().usingCentralBank()) {
return;
}
// load the transactions so we can check the server bank
List<History> transactions = protection.getRelatedHistory(History.Type.TRANSACTION);
for (History history : transactions) {
if (history.getStatus() == History.Status.INACTIVE) {
continue;
}
// obtain the charge
double charge = history.getDouble("charge");
// No need to refund if it's negative or 0
if (charge <= 0) {
continue;
}
// check the server bank
if (!lwc.getCurrency().canCentralBankAfford(charge)) {
player.sendMessage(Colors.Red + "The Server's Bank does not contain enough funds to remove that protection!");
event.setCancelled(true);
return;
}
}
}
@Override
public void onPostRegistration(LWCProtectionRegistrationPostEvent event) {
if (!configuration.getBoolean("iConomy.enabled", true)) {
return;
}
if (!LWC.getInstance().isHistoryEnabled()) {
return;
}
Protection protection = event.getProtection();
// we need to inject the iconomy price into the transaction!
Block block = protection.getBlock();
// Uh-oh! This REALLY should never happen ... !
if (block == null || !priceCache.containsKey(block.getLocation())) {
return;
}
Location location = block.getLocation();
// okey, get how much they were charged
String cachedPrice = priceCache.get(location);
boolean usedDiscount = cachedPrice.startsWith("d");
double charge = Double.parseDouble(usedDiscount ? cachedPrice.substring(1) : cachedPrice);
// get related transactions..
List<History> transactions = protection.getRelatedHistory(History.Type.TRANSACTION);
// this really should not happen either (never!)
if (transactions.size() == 0) {
logger.severe("LWC-iConomy POST_REGISTRATION encountered a severen problem!: transactions.size() == 0");
}
// get the last entry
History history = transactions.get(transactions.size() - 1);
// add the price
history.addMetaData("charge=" + charge);
// was it a discount?
if (usedDiscount) {
history.addMetaData("discount=true");
// Was the discount's id non-null?
String discountId = resolveValue(protection.getBukkitOwner(), "discount.id");
if (!discountId.isEmpty()) {
history.addMetaData("discountId=" + discountId);
}
}
// save it immediately
history.saveNow();
// we no longer need the value in the price cache :)
priceCache.remove(location);
}
@Override
public void onPostRemoval(LWCProtectionRemovePostEvent event) {
if (!configuration.getBoolean("iConomy.enabled", true)) {
return;
}
// is refunding enabled?
if (!configuration.getBoolean("iConomy.refunds", true)) {
return;
}
if (!LWC.getInstance().isHistoryEnabled()) {
return;
}
LWC lwc = event.getLWC();
Protection protection = event.getProtection();
// first, do we still have a currency processor?
if (!lwc.getCurrency().isActive()) {
return;
}
// we need to refund them, load up transactions
List<History> transactions = protection.getRelatedHistory(History.Type.TRANSACTION);
for (History history : transactions) {
if (history.getStatus() == History.Status.INACTIVE) {
continue;
}
// obtain the charge
double charge = history.getDouble("charge");
// No need to refund if it's negative or 0
if (charge <= 0) {
continue;
}
// refund them :)
Player owner = Bukkit.getServer().getPlayer(history.getPlayer());
// we can't pay them ..
if (owner == null) {
continue;
}
// the currency to use
ICurrency currency = lwc.getCurrency();
currency.addMoney(owner, charge);
owner.sendMessage(Colors.Green + "You have been refunded " + currency.format(charge) + " because an LWC protection of yours was removed!");
}
}
@Override
public void onRegisterProtection(LWCProtectionRegisterEvent event) {
if (event.isCancelled()) {
return;
}
if (!configuration.getBoolean("iConomy.enabled", true)) {
return;
}
LWC lwc = event.getLWC();
Block block = event.getBlock();
Player player = event.getPlayer();
// currency handler to use
ICurrency currency = lwc.getCurrency();
if (!currency.isActive()) {
return;
}
// if a discount was used
boolean usedDiscount = false;
// how much to charge the player
double charge = 0D;
String value;
// Check for a block override charge
if ((value = lwc.resolveProtectionConfiguration(block, "charge")) != null) {
try {
charge = Double.parseDouble(value);
} catch (NumberFormatException e) {
}
} else {
charge = resolveDouble(player, "charge", false);
}
// check if they have a discount available
try {
boolean isDiscountActive = Boolean.parseBoolean(resolveValue(player, "discount.active"));
if (isDiscountActive) {
int discountedProtections = resolveInt(player, "discount.amount", true);
double discountPrice = resolveDouble(player, "discount.newCharge", false);
if (discountedProtections > 0) {
int currentProtections = 0;
// Match the discount type
DiscountType discountType = DiscountType.valueOf(resolveValue(player, "discount.type").toUpperCase());
// The unique id of the discount, by default they are shared between discounts if not set
String discountId = resolveValue(player, "discount.id");
// Count the protections
switch (discountType) {
case EXACT:
currentProtections = countDiscountedProtections(lwc, player, discountPrice, discountId.isEmpty() ? null : discountId, false);
break;
case TOTAL:
currentProtections = lwc.getPhysicalDatabase().getProtectionCount(player.getName());
break;
case IN_USE:
currentProtections = countDiscountedProtections(lwc, player, discountPrice, discountId.isEmpty() ? null : discountId, true);
break;
}
if (discountedProtections > currentProtections) {
charge = discountPrice;
usedDiscount = true;
}
}
}
} catch (NumberFormatException e) {
}
// used for price cache
Location location = block.getLocation();
// cache the charge momentarily
if (lwc.isHistoryEnabled()) {
priceCache.put(location, (usedDiscount ? "d" : "") + charge);
}
// It's free!
if (charge == 0) {
player.sendMessage(Colors.Green + "This one's on us!");
return;
}
// charge them
if (charge != 0) {
if (!currency.canAfford(player, charge)) {
player.sendMessage(Colors.Red + "You do not have enough " + currency.getMoneyName() + " to buy an LWC protection.");
player.sendMessage(Colors.Red + "The balance required for an LWC protection is: " + currency.format(charge));
// remove from cache
priceCache.remove(location);
event.setCancelled(true);
return;
}
// remove the money from their account
currency.removeMoney(player, charge);
player.sendMessage(Colors.Green + "Charged " + currency.format(charge) + (usedDiscount ? (Colors.Red + " (Discount)" + Colors.Green) : "") + " for an LWC protection. Thank you.");
return;
}
}
/**
* Resolve a configuration node for a player. Tries nodes in this order:
* <pre>
* players.PLAYERNAME.node
* groups.GROUPNAME.node
* iConomy.node
* </pre>
*
* @param player
* @param node
* @param sortHighest
* @return
*/
private int resolveInt(Player player, String node, boolean sortHighest) {
return (int) resolveDouble(player, node, sortHighest);
}
/**
* Resolve a configuration node for a player. Tries nodes in this order:
* <pre>
* players.PLAYERNAME.node
* groups.GROUPNAME.node
* iConomy.node
* </pre>
*
* @param player
* @param node
* @param sortHighest sort values by highest value
* @return
*/
private double resolveDouble(Player player, String node, boolean sortHighest) {
LWC lwc = LWC.getInstance();
double value = -1;
String cacheKey = "resolve-" + player.getName() + node;
if (cache.containsKey(cacheKey)) {
return Double.parseDouble(cache.get(cacheKey));
}
// try the player
try {
value = Double.parseDouble(configuration.getString("players." + player.getName() + "." + node, "-1"));
} catch (NumberFormatException e) { } // May I be forgiven in hell for that
// try their groups
if (value == -1) {
for (String groupName : lwc.getPermissions().getGroups(player)) {
if (groupName != null && !groupName.isEmpty()) {
try {
double v = Double.parseDouble(map("groups." + groupName + "." + node, "-1"));
if (v == -1) {
continue;
}
// check the value
if (sortHighest && (v > value || value == -1)) {
value = v;
} else if (!sortHighest && (v < value || value == -1)) {
value = v;
}
} catch (NumberFormatException e) { }
}
}
}
// if all else fails, use master
if (value == -1) {
try {
value = Double.parseDouble(map("iConomy." + node, "-1"));
} catch (NumberFormatException e) { }
}
cache.put(cacheKey, Double.toString(value));
return value;
}
/**
* Resolve a configuration node for a player. Tries nodes in this order:
* <pre>
* players.PLAYERNAME.node
* groups.GROUPNAME.node
* iConomy.node
* </pre>
*
* @param player
* @param node
* @return
*/
private String resolveValue(Player player, String node) {
LWC lwc = LWC.getInstance();
// resolve the limits type
String value;
// try the player
value = configuration.getString("players." + player.getName() + "." + node, null);
// try permissions
if (value == null) {
for (String groupName : lwc.getPermissions().getGroups(player)) {
if (groupName != null && !groupName.isEmpty() && value == null) {
value = map("groups." + groupName + "." + node, null);
}
}
}
// if all else fails, use master
if (value == null) {
value = map("iConomy." + node, null);
}
return value != null && !value.isEmpty() ? value : "";
}
/**
* Get the value from either the path or the default value if it's null
*
* @param path
* @return
*/
private String map(String path, String defaultValue) {
String value = configuration.getString(path, defaultValue);
if (value == null) {
int lastIndex = path.lastIndexOf(".");
String node = path.substring(lastIndex + 1);
value = configuration.getString("iConomy." + node);
}
return value;
}
/**
* Get the amount of protections the player purchased for the given discount price
*
* @param lwc
* @param player
* @param discountPrice
* @param discountId
* @param onlyCountActiveTransactions
* @return
*/
private int countDiscountedProtections(LWC lwc, Player player, double discountPrice, String discountId, boolean onlyCountActiveTransactions) {
LWCPlayer lwcPlayer = lwc.wrapPlayer(player);
List<History> related = lwcPlayer.getRelatedHistory(History.Type.TRANSACTION);
int amount = 0;
for (History history : related) {
if (!history.getBoolean("discount")) {
continue;
}
// Check the other discount id
if (discountId != null) {
if (!history.hasKey("discountId") || !history.getString("discountId").equals(discountId)) {
continue;
}
}
// Are we only looking for valid transactions?
if (onlyCountActiveTransactions && history.getStatus() == History.Status.INACTIVE) {
continue;
}
// obtain the charge
double charge = history.getDouble("charge");
if (charge == discountPrice) {
amount++;
}
}
return amount;
}
}