/* * ExperienceMod - Bukkit server plugin for modifying the experience system in Minecraft. * Copyright (C) 2012 Kristian S. Stangeland * * This program is free software; you can redistribute it and/or modify it under the terms of the * GNU General Public License as published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with this program; * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA * 02111-1307 USA */ package com.comphenix.xp; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import com.comphenix.xp.expressions.ParameterProviderSet; import com.comphenix.xp.listeners.ErrorReporting; import com.comphenix.xp.listeners.PlayerCleanupListener; import com.comphenix.xp.lookup.*; import com.comphenix.xp.messages.ChannelProvider; import com.comphenix.xp.messages.MessagePlayerQueue; import com.comphenix.xp.parser.ActionParser; import com.comphenix.xp.parser.DoubleParser; import com.comphenix.xp.parser.StringListParser; import com.comphenix.xp.parser.Utility; import com.comphenix.xp.parser.ParsingException; import com.comphenix.xp.parser.sections.*; import com.comphenix.xp.parser.text.ItemParser; import com.comphenix.xp.parser.text.MobParser; import com.comphenix.xp.parser.text.PlayerParser; import com.comphenix.xp.rewards.ResourceFactory; import com.comphenix.xp.rewards.RewardProvider; import com.comphenix.xp.rewards.RewardTypes; public class Configuration implements PlayerCleanupListener, Multipliable<Configuration> { private static final String MULTIPLIER_SETTING = "multiplier"; private static final String DISABLE_REWARDS_SETTING = "default rewards disabled"; private static final String REWARD_TYPE_SETTING = "reward type"; private static final String ECONOMY_DROPS_SETTING = "economy drop"; private static final String ECONOMY_WORTH_SETTING = "economy drop worth"; private static final String VIRTUAL_SCAN_RADIUS_SETTING = "virtual scan radius"; private static final String DEFAULT_CHANNELS_SETTING = "default channels"; private static final String MESSAGE_MAX_RATE_SETTING = "message max rate"; private static final String MAXIMUM_ENCHANT_LEVEL_SETTING = "maximum enchant level"; private static final String MAXIMUM_BOOKCASE_COUNT_SETTING = "maximum bookcase count"; public static final int DEFAULT_ECONOMY_WORTH = 1; public static final ItemStack DEFAULT_ECONOMY_DROP = null; public static final boolean DEFAULT_DISABLE_REWARDS = false; public static final double DEFAULT_SCAN_RADIUS = 20; public static final int DEFAULT_MESSAGE_RATE = 5; public static final int DEFAULT_MAXIMUM_ENCHANT_LEVEL = 30; public static final int DEFAULT_MAXIMUM_BOOKCASE_COUNT = 15; public static final int MAXIMUM_BOOKCASE_COUNT = 255; private Debugger logger; private double multiplier; private boolean defaultRewardsDisabled = DEFAULT_DISABLE_REWARDS; private boolean preset; private ItemStack economyDropItem = DEFAULT_ECONOMY_DROP; private Integer economyItemWorth = DEFAULT_ECONOMY_WORTH; private double scanRadiusSetting = DEFAULT_SCAN_RADIUS; private RewardProvider rewardProvider; private ChannelProvider channelProvider; private ParameterProviderSet parameterProviders; private MessagePlayerQueue messageQueue; private LevelingRate levelingRate; private int maximumEnchantLevel = DEFAULT_MAXIMUM_ENCHANT_LEVEL; private int maximumBookcaseCount = DEFAULT_MAXIMUM_BOOKCASE_COUNT; // Global settings private GlobalSettings globalSettings; // Every action/trigger type private ActionTypes actionTypes; // Every standard reward private Map<Integer, ItemTree> actionRewards = new HashMap<Integer, ItemTree>(); private Map<Integer, PotionTree> complexRewards = new HashMap<Integer, PotionTree>(); private PlayerTree playerDeathDrop; private MobTree experienceDrop; private PlayerRewards playerRewards; private DoubleParser doubleParser = new DoubleParser(); private ItemParser itemParser; private MobParser mobParser; private ActionParser actionParser; private PlayerParser playerParser; // Configuration sections private Set<String> topLevel; public Configuration(Debugger debugger, ActionTypes actionTypes) { this.logger = debugger; this.defaultRewardsDisabled = false; this.actionTypes = actionTypes; initialize(1); } public Configuration(Configuration other, double newMultiplier) { if (other == null) throw new IllegalArgumentException("other"); // Copy (and change) scalars this.multiplier = newMultiplier; this.logger = other.logger; this.maximumEnchantLevel = other.maximumEnchantLevel; this.maximumBookcaseCount = other.maximumBookcaseCount; this.defaultRewardsDisabled = other.defaultRewardsDisabled; this.economyItemWorth = other.economyItemWorth; this.economyDropItem = other.economyDropItem; this.scanRadiusSetting = other.scanRadiusSetting; this.parameterProviders = other.parameterProviders; this.rewardProvider = other.rewardProvider; this.channelProvider = other.channelProvider; this.actionParser = other.actionParser; this.messageQueue = other.messageQueue; this.initializeReferences(); // Copy parsers this.itemParser = other.itemParser; this.mobParser = other.mobParser; this.actionTypes = other.actionTypes; this.playerParser = other.playerParser; // Copy (shallow) trees this.actionRewards = copyActionsWithMultiplier(other.actionRewards, newMultiplier); this.complexRewards = copyActionsWithMultiplier(other.complexRewards, newMultiplier); this.experienceDrop = other.experienceDrop.withMultiplier(newMultiplier); this.playerDeathDrop = other.playerDeathDrop.withMultiplier(newMultiplier); this.playerRewards = other.playerRewards.withMultiplier(newMultiplier); this.levelingRate = other.levelingRate; this.checkRewards(); } public Configuration(Debugger debugger, RewardProvider provider, ChannelProvider channels) { this.logger = debugger; this.rewardProvider = provider; this.channelProvider = channels; this.actionParser = new ActionParser(provider); this.playerParser = new PlayerParser(); } // Makes a copy of a action reward tree private static <TParam extends Multipliable<TParam>> Map<Integer, TParam> copyActionsWithMultiplier( Map<Integer, TParam> rewards, double newMultiplier) { Map<Integer, TParam> copy = new HashMap<Integer, TParam>(); // Treat null as empty if (rewards == null) return copy; for (Entry<Integer, TParam> entry : rewards.entrySet()) { copy.put(entry.getKey(), entry.getValue().withMultiplier(newMultiplier)); } return copy; } // Merges action reward trees private static <TParam, TTree extends ActionTree<TParam>> void mergeActions( Map<Integer, TTree> destination, Map<Integer, TTree> source) { // Merge all trees for (Entry<Integer, TTree> entry : source.entrySet()) { TTree tree = destination.get(entry.getKey()); Integer key = entry.getKey(); TTree value = entry.getValue(); if (tree == null) destination.put(key, value); else destination.get(key).putAll(value); } } private void initializeReferences() { if (rewardProvider != null) { rewardProvider = rewardProvider.createView(this); } if (channelProvider != null) { channelProvider = channelProvider.createView(); } if (messageQueue != null) { messageQueue = messageQueue.createView(); messageQueue.setChannelProvider(channelProvider); } if (actionParser != null) { actionParser = actionParser.createView(rewardProvider); } } /** * Merge a list of configurations into a new configuration. * * @param configurations - list of configurations. * @param debugger - debugger instance. * @return Merged configuration. */ public static Configuration fromMultiple(List<Configuration> configurations, Debugger debugger) { if (configurations == null) return null; else if (configurations.size() == 0) return null; else if (configurations.size() == 1) return configurations.get(0); Configuration copy = null; // Merge everything in order for (Configuration config : configurations) { // Initialize first time around if (copy == null) { copy = new Configuration(debugger, config.actionTypes); } copy.experienceDrop.putAll(config.experienceDrop); copy.playerDeathDrop.putAll(config.playerDeathDrop); copy.playerRewards.putAll(config.playerRewards); copy.levelingRate.putAll(config.levelingRate); mergeActions(copy.actionRewards, config.actionRewards); mergeActions(copy.complexRewards, config.complexRewards); // This will be the last set non-standard value if (config.hadConfiguration(DISABLE_REWARDS_SETTING)) copy.defaultRewardsDisabled = config.defaultRewardsDisabled; if (config.hadConfiguration(MAXIMUM_ENCHANT_LEVEL_SETTING)) copy.maximumEnchantLevel = config.maximumEnchantLevel; if (config.hadConfiguration(MAXIMUM_BOOKCASE_COUNT_SETTING)) copy.maximumBookcaseCount = config.maximumBookcaseCount; if (config.hadConfiguration(VIRTUAL_SCAN_RADIUS_SETTING)) copy.scanRadiusSetting = config.scanRadiusSetting; if (config.hadConfiguration(ECONOMY_WORTH_SETTING)) copy.economyItemWorth = config.economyItemWorth; if (config.hadConfiguration(ECONOMY_DROPS_SETTING)) copy.economyDropItem = config.economyDropItem; if (copy.messageQueue == null || config.hadConfiguration(MESSAGE_MAX_RATE_SETTING)) copy.messageQueue = config.messageQueue; if (copy.rewardProvider == null || config.hadConfiguration(REWARD_TYPE_SETTING)) copy.rewardProvider = config.rewardProvider; if (copy.channelProvider == null || config.hadConfiguration(DEFAULT_CHANNELS_SETTING)) copy.channelProvider = config.channelProvider; copy.parameterProviders = config.parameterProviders; copy.itemParser = config.itemParser; copy.mobParser = config.mobParser; copy.actionParser = config.actionParser; copy.playerParser = config.playerParser; // Multiply all multipliers copy.multiplier *= config.multiplier; } // Update multiplier return new Configuration(copy, copy.multiplier); } /** * Determine whether or not the given configuration setting was specified. * @param name - name of the setting. * @return TRUE if it was, FALSE otherwise. */ protected boolean hadConfiguration(String name) { if (topLevel == null) return false; else return topLevel.contains(name); } /** * Initialize configuration from a configuration section. * @param config - configuration section to load from. */ public void loadFromConfig(ConfigurationSection config) { // Keep track of top-level types topLevel = config.getValues(false).keySet(); // Load scalar values first multiplier = doubleParser.parse(config, MULTIPLIER_SETTING, 1.0); // Initialize parsers MobSectionParser mobsParser = new MobSectionParser( actionParser, mobParser, parameterProviders, multiplier); PlayerDeathSectionParser playerDeathParser = new PlayerDeathSectionParser( actionParser, playerParser, parameterProviders, multiplier); ItemsSectionParser itemsParser = new ItemsSectionParser( itemParser, actionParser, actionTypes, parameterProviders, multiplier); PlayerSectionParser playerParser = new PlayerSectionParser( actionParser, parameterProviders, multiplier); LevelsSectionParser levelsParser = new LevelsSectionParser(); // Set debugger mobsParser.setDebugger(logger); itemsParser.setDebugger(logger); playerParser.setDebugger(logger); levelsParser.setDebugger(logger); playerDeathParser.setDebugger(logger); // Enchanting settings maximumEnchantLevel = config.getInt(MAXIMUM_ENCHANT_LEVEL_SETTING, DEFAULT_MAXIMUM_ENCHANT_LEVEL); maximumBookcaseCount = config.getInt(MAXIMUM_BOOKCASE_COUNT_SETTING, DEFAULT_MAXIMUM_BOOKCASE_COUNT); // There's a limit to things if (maximumBookcaseCount > MAXIMUM_BOOKCASE_COUNT) { maximumBookcaseCount = MAXIMUM_BOOKCASE_COUNT; logger.printWarning(this, "Maximum bookcase count cannot exceed 255."); } // Whether or not to remove all default XP drops defaultRewardsDisabled = config.getBoolean(DISABLE_REWARDS_SETTING, DEFAULT_DISABLE_REWARDS); scanRadiusSetting = doubleParser.parse(config, VIRTUAL_SCAN_RADIUS_SETTING, DEFAULT_SCAN_RADIUS); // Economy item settings economyItemWorth = config.getInt(ECONOMY_WORTH_SETTING, DEFAULT_ECONOMY_WORTH); economyDropItem = DEFAULT_ECONOMY_DROP; // Economy drop item try { String text = config.getString(ECONOMY_DROPS_SETTING, null); Query drop = text != null ? itemParser.parse(text) : null; if (drop != null && drop instanceof ItemQuery) { economyDropItem = ((ItemQuery) drop).toItemStack(1); } } catch (ParsingException e) { logger.printWarning(this, "Cannot load economy drop type: %s", e.getMessage()); } // Default message channels StringListParser listParser = new StringListParser(); channelProvider.setDefaultChannels(listParser.parseSafe(config, DEFAULT_CHANNELS_SETTING)); // Load reward type String defaultReward = loadReward(config.getString(REWARD_TYPE_SETTING, null)); // Use default type if nothing has been set if (defaultReward != null) setDefaultRewardName(defaultReward); loadRate(config); try { // Load mob experience experienceDrop = mobsParser.parse(config, "mobs"); // Load player death experience playerDeathDrop = playerDeathParser.parse(config, "player death"); // Load items and potions ItemsSectionResult result = itemsParser.parse(config, "items"); actionRewards = result.getActionRewards(); complexRewards = result.getComplexRewards(); // Load player rewards playerRewards = playerParser.parse(config, "player"); // Load custom levels levelingRate = levelsParser.parse(config, "levels"); } catch (ParsingException e) { // This must be because a debugger isn't attached. Damn it. ErrorReporting.DEFAULT.reportError(logger, this, e); } // Reload providers initializeReferences(); checkRewards(); } private void loadRate(ConfigurationSection config) { // Load the message queue double rate = doubleParser.parse(config, MESSAGE_MAX_RATE_SETTING, (double) DEFAULT_MESSAGE_RATE); long converted = 0; // Make sure the rate is valid if (rate * 1000 > Long.MAX_VALUE) logger.printWarning(this, "Message rate cannot be bigger than %d", Long.MAX_VALUE / 1000); else if (rate < 0) logger.printWarning(this, "Message rate cannot be negative."); else converted = (long) (rate * 1000); // Always create a message queue messageQueue = new MessagePlayerQueue(converted, channelProvider, logger); } private void initialize(double multiplier) { // Clear previous values experienceDrop = new MobTree(multiplier); playerDeathDrop = new PlayerTree(multiplier); // Initialize all the default rewards for (Integer types : actionTypes.getTypes()) { actionRewards.put(types, new ItemTree(multiplier)); complexRewards.put(types, new PotionTree(multiplier)); } playerParser = new PlayerParser(); playerRewards = new PlayerRewards(multiplier); this.levelingRate = new LevelingRate(); this.multiplier = multiplier; } public Collection<Action> getActions() { List<Action> actions = new ArrayList<Action>(); // Add every simple action for (Integer types : actionTypes.getTypes()) { ItemTree items = getActionReward(types); PotionTree potions = getComplexReward(types); if (items != null) actions.addAll(items.getValues()); if (potions != null) actions.addAll(potions.getValues()); } // Copy the content of every special collection actions.addAll(experienceDrop.getValues()); actions.addAll(playerDeathDrop.getValues()); actions.addAll(playerRewards.getValues()); return actions; } private void checkRewards() { Collection<Action> actions = getActions(); // Are any rewards negative if (hasNegativeRewards(actions)) { logger.printWarning(this, "Cannot use negative rewards with the experience reward type."); } // Economy rewards when no economy is registered if (rewardProvider.getByEnum(RewardTypes.ECONOMY) == null && hasEconomyReward(actions)) { logger.printWarning(this, "VAULT was not found. Cannot use economy."); } } private boolean hasEconomyReward(Collection<Action> values) { // See if we have an economy reward set for (Action action : values) { if (action.getReward("ECONOMY") != null) return true; } return false; } private boolean hasNegativeRewards(Collection<Action> values) { // Check every range for (Action action : values) { ResourceFactory factory = action.getReward("EXPERIENCE"); if (factory != null && (factory.getMinimum(null, 1).getAmount() < 0)) return true; } return false; } private String loadReward(String text) { String parsing = Utility.getEnumName(text); // Load reward name if (text != null && rewardProvider.containsService(parsing)) { return parsing; } else { return null; } } /** * Whether or not this configuration is associated with a specified preset. * @return TRUE if it is, FALSE otherwise. */ public boolean hasPreset() { return preset; } /** * Whether or not this configuration is associated with a specified preset. * @param value - new value. */ public void setPreset(boolean value) { preset = value; } public GlobalSettings getGlobalSettings() { return globalSettings; } public void setGlobalSettings(GlobalSettings globalSettings) { this.globalSettings = globalSettings; } /** * Overrides the rate at which players gain levels. * @return Custom rules for how much experience a player needs to level up. */ public LevelingRate getLevelingRate() { return levelingRate; } @Override public Configuration withMultiplier(double newMultiplier) { return new Configuration(this, newMultiplier); } @Override public double getMultiplier() { return multiplier; } public MobTree getExperienceDrop() { return experienceDrop; } /** * Retrieves the rewards for the given action or trigger. * @param actionID - unique ID for the given action. * @return Tree of every associated reward. */ public ItemTree getActionReward(Integer actionID) { return actionRewards.get(actionID); } /** * Retrieves the rewards for the given action or trigger. * @param action - name for the given action. * @return Tree of every associated reward. */ public ItemTree getActionReward(String action) { return getActionReward(actionTypes.getType(action)); } /** * Sets the tree of rewards for a given action. * @param actionID - unique ID for the given action. * @return Previously associated tree of rewards, or NULL if no tree was associated. */ public ItemTree setActionReward(Integer actionID, ItemTree tree) { return actionRewards.put(actionID, tree); } /** * Retrieves the complex potion rewards for the given action or trigger. * @param actionID - unique ID for the given action. * @return Tree of every associated reward. */ public PotionTree getComplexReward(Integer actionID) { return complexRewards.get(actionID); } /** * Retrieves the complex potion rewards for the given action or trigger. * @param action - name for the given action. * @return Tree of every associated reward. */ public PotionTree getComplexReward(String action) { return getComplexReward(actionTypes.getType(action)); } /** * Sets the tree of rewards for a given action. * @param actionID - unique ID for the given action. * @return Previously associated tree of rewards, or NULL if no tree was associated. */ public PotionTree setComplexReward(Integer actionID, PotionTree tree) { return complexRewards.put(actionID, tree); } public boolean isDefaultRewardsDisabled() { return defaultRewardsDisabled; } public int getMaximumEnchantLevel() { return maximumEnchantLevel; } public int getMaximumBookcaseCount() { return maximumBookcaseCount; } public PlayerRewards getPlayerRewards() { return playerRewards; } public RewardProvider getRewardProvider() { return rewardProvider; } public ChannelProvider getChannelProvider() { return channelProvider; } public void setRewardManager(RewardProvider rewardProvider) { this.rewardProvider = rewardProvider; } public MessagePlayerQueue getMessageQueue() { return messageQueue; } public ItemStack getEconomyDropItem() { return economyDropItem; } public Integer getEconomyItemWorth() { return economyItemWorth; } public String getDefaultRewardName() { if (rewardProvider == null) return null; else return rewardProvider.getDefaultName(); } public void setDefaultRewardName(String rewardName) { if (rewardProvider != null) { rewardProvider.setDefaultName(rewardName); } } public ItemTree getSimpleBlockReward() { return getActionReward(actionTypes.getType(ActionTypes.BLOCK)); } public ItemTree getSimpleBonusReward() { return getActionReward(actionTypes.getType(ActionTypes.BONUS)); } public ItemTree getSimpleBrewingReward() { return getActionReward(actionTypes.getType(ActionTypes.BREWING)); } public ItemTree getSimpleCraftingReward() { return getActionReward(actionTypes.getType(ActionTypes.CRAFTING)); } public ItemTree getSimplePlacingReward() { return getActionReward(actionTypes.getType(ActionTypes.PLACE)); } public ItemTree getSimpleSmeltingReward() { return getActionReward(actionTypes.getType(ActionTypes.SMELTING)); } public PotionTree getComplexBrewingReward() { return getComplexReward(actionTypes.getType(ActionTypes.BREWING)); } public double getScanRadiusSetting() { return scanRadiusSetting; } /** * Retrieves the current registered action types. * @return Registry of action types. */ public ActionTypes getActionTypes() { return actionTypes; } /** * Sets the current registry of action types. This must be changed before configurations are loaded. * @param actionTypes - new action type registry. */ public void setActionTypes(ActionTypes actionTypes) { this.actionTypes = actionTypes; } public ItemParser getItemParser() { return itemParser; } public void setItemParser(ItemParser itemParser) { this.itemParser = itemParser; } public MobParser getMobParser() { return mobParser; } public void setMobParser(MobParser mobParser) { this.mobParser = mobParser; } public ActionParser getActionParser() { return actionParser; } public void setActionParser(ActionParser actionParser) { this.actionParser = actionParser; } public PlayerTree getPlayerDeathDrop() { return playerDeathDrop; } public void setPlayerDeathDrop(PlayerTree playerDeathDrop) { this.playerDeathDrop = playerDeathDrop; } public PlayerParser getPlayerParser() { return playerParser; } public void setPlayerParser(PlayerParser playerParser) { this.playerParser = playerParser; } public ParameterProviderSet getParameterProviders() { return parameterProviders; } public void setParameterProviders(ParameterProviderSet parameterProviders) { this.parameterProviders = parameterProviders; } // Let the message queue know public void onTick() { if (messageQueue != null) messageQueue.onTick(); } @Override public void removePlayerCache(Player player) { // Removes a given player from any live buffers or caches. if (messageQueue != null) messageQueue.removePlayerCache(player); } }