package toadmess.explosives.config.entity; import static toadmess.explosives.config.ConfProps.*; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Random; import java.util.logging.Logger; import org.bukkit.util.config.ConfigurationNode; import toadmess.explosives.Bounds; import toadmess.explosives.MCNative; import toadmess.explosives.config.ConfProps; public class EntityConfReader { private final Logger log; private final ConfigurationNode conf; private final String confPathPrefix; private final EntityConf parent; private final Random rng; public EntityConfReader(final ConfigurationNode conf, final String confPathPrefix, final Logger log) { this(conf, confPathPrefix, log, null, new Random()); } public EntityConfReader(final ConfigurationNode conf, final String confPathPrefix, final Logger log, final EntityConf parent) { this(conf, confPathPrefix, log, parent, new Random()); } public EntityConfReader(final ConfigurationNode conf, final String confPathPrefix, final Logger log, final EntityConf parent, final Random rng) { this.log = log; this.conf = conf; this.confPathPrefix = confPathPrefix; this.parent = parent; this.rng = rng; } public EntityConf readEntityConf() { final EntityConf ec = new EntityConf(this.parent, this.rng); final Object[] properties = new Object[ConfProps.values().length]; readBounds(properties); readMultipliers(properties, CONF_ENTITY_RADIUSMULT); readMultipliers(properties, CONF_ENTITY_PLAYER_DAMAGEMULT); readMultipliers(properties, CONF_ENTITY_CREATURE_DAMAGEMULT); readMultipliers(properties, CONF_ENTITY_ITEM_DAMAGEMULT); readMultipliers(properties, CONF_ENTITY_TNT_FUSEMULT); readProperty(properties, CONF_ENTITY_FIRE); readProperty(properties, CONF_ENTITY_PREVENT_TERRAIN_DAMAGE); readOptionalFloat(properties, CONF_ENTITY_YIELD); readSpecificYields(properties); readProperty(properties, CONF_ENTITY_TNT_TRIGGER_PREVENTED); readSubConfig(ec, properties, CONF_ENTITY_TNT_TRIGGER_HAND); readSubConfig(ec, properties, CONF_ENTITY_TNT_TRIGGER_FIRE); readSubConfig(ec, properties, CONF_ENTITY_TNT_TRIGGER_REDSTONE); readSubConfig(ec, properties, CONF_ENTITY_TNT_TRIGGER_EXPLOSION); readSubConfig(ec, properties, CONF_ENTITY_CREEPER_CHARGED); readConfigListWithChainedInheritance(ec, properties, CONF_PERMISSIONS_LIST); properties[CONF_PERMISSIONS_NODE_NAME.ordinal()] = this.conf.getProperty(CONF_PERMISSIONS_NODE_NAME.toString()); properties[CONF_PERMISSIONS_GROUP_NAME.ordinal()] = this.conf.getProperty(CONF_PERMISSIONS_GROUP_NAME.toString()); if(null != this.conf.getProperty(confPathPrefix + ".trialTNTFuseMultiplier")) { this.log.warning("HigherExplosives: The \"trialTNTFuseMultiplier\" configuration is no longer used. Please rename it to \"" + CONF_ENTITY_TNT_FUSEMULT + "\""); } if(null != this.conf.getProperty("everyExplosion") || null != this.conf.getProperty(confPathPrefix + ".everyExplosion")) { this.log.warning("HigherExplosives: The \"everyExplosion\" configuration is no longer used. Instead, specify the explosion \"yield\" on the individual entities."); } ec.setProperties(properties); return ec; } private void readSubConfig(final EntityConf parent, final Object[] properties, final ConfProps confProperty) { final String confPath = this.confPathPrefix + "." + confProperty.toString(); if(this.conf.getProperty(confPath) == null) { // There is no sub entity config at this configuration path properties[confProperty.ordinal()] = null; } else { final EntityConfReader ecr = new EntityConfReader(this.conf, confPath, this.log, parent); properties[confProperty.ordinal()] = ecr.readEntityConf(); } } private void readConfigListWithChainedInheritance(final EntityConf parent, final Object[] properties, final ConfProps confProperty) { final String confPath = this.confPathPrefix + "." + confProperty.toString(); final Object confProp = this.conf.getProperty(confPath); if(this.conf.getProperty(confPath) == null) { // There is no sub entity config at this configuration path properties[confProperty.ordinal()] = null; } else { if (!(confProp instanceof List<?>)) { this.log.warning("HigherExplosives: Config problem. Ignoring "+confProperty+" property under " + confPath + ". Was expecting a list of permission node names and their configs."); return; } final ArrayList<EntityConf> listConfs = new ArrayList<EntityConf>(); EntityConf inheritedParent = this.parent; for(final ConfigurationNode confNode : conf.getNodeList(confPath, null)) { final List<String> permissionConfKeys = confNode.getKeys(null); final boolean hasPermConfig = permissionConfKeys.contains(CONF_PERMISSIONS_CONFIG.toString()); final boolean hasPermName = permissionConfKeys.contains(CONF_PERMISSIONS_NODE_NAME.toString()); final boolean hasGroupName = permissionConfKeys.contains(CONF_PERMISSIONS_GROUP_NAME.toString()); if(!hasPermName && !hasGroupName) { this.log.warning("HigherExplosives: Config problem under " + confPath); this.log.warning("HigherExplosives: An item in the \"" + confProperty + "\" list is missing either a \""+CONF_PERMISSIONS_NODE_NAME+"\" or \""+CONF_PERMISSIONS_GROUP_NAME+"\" property"); return; } if(!hasPermConfig) { final String problemPermName = confNode.getString((hasPermName ? CONF_PERMISSIONS_NODE_NAME : CONF_PERMISSIONS_GROUP_NAME).toString()); this.log.warning("HigherExplosives: Config problem under " + confPath); this.log.warning("HigherExplosives: The " + problemPermName + " item in the \"" + confProperty + "\" list is missing a \""+CONF_PERMISSIONS_CONFIG+"\" property"); return; } final EntityConfReader ecr = new EntityConfReader(confNode, CONF_PERMISSIONS_CONFIG.toString(), this.log, inheritedParent); final EntityConf permissionBasedConfig = ecr.readEntityConf(); listConfs.add(permissionBasedConfig); inheritedParent = permissionBasedConfig; } listConfs.trimToSize(); properties[confProperty.ordinal()] = (listConfs.size() == 0) ? null : listConfs; } } private void readProperty(final Object[] properties, final ConfProps confProperty) { properties[confProperty.ordinal()] = this.conf.getProperty(this.confPathPrefix + "." + confProperty); } private void readOptionalFloat(final Object[] properties, final ConfProps confProperty) { final String path = this.confPathPrefix + "." + confProperty; final Object o = this.conf.getProperty(path); if(o == null || !(o instanceof Number)) { properties[confProperty.ordinal()] = null; } else { properties[confProperty.ordinal()] = Double.valueOf(this.conf.getDouble(path, 0.0D)).floatValue(); } } private void readBounds(final Object[] properties) { if(this.parent != null) { // Make all sub-configs inherit bounds from the parent properties[CONF_BOUNDS.ordinal()] = null; } else { properties[CONF_BOUNDS.ordinal()] = new Bounds(this.conf, this.confPathPrefix); } } /** * @return A sparse array whose index is a specific block type ID and whose value is the yield * percentage (from 0.0 to 1.0) to use for that block type. */ private void readSpecificYields(final Object[] properties) { final String pathToYieldSpecific = this.confPathPrefix + "." + CONF_ENTITY_YIELD_SPECIFIC; final Object specYieldsProp = this.conf.getProperty(pathToYieldSpecific); if (specYieldsProp instanceof HashMap<?,?>) { final HashMap<?,?> specYields = (HashMap<?,?>) specYieldsProp; final Float[] yieldsSparseArr = new Float[MCNative.getHighestBlockId()+1]; for(final Object blockID : specYields.keySet()) { yieldsSparseArr[(Integer) blockID] = ((Number) specYields.get(blockID)).floatValue(); } properties[CONF_ENTITY_YIELD_SPECIFIC.ordinal()] = yieldsSparseArr; } else { properties[CONF_ENTITY_YIELD_SPECIFIC.ordinal()] = null; } } /** * Extracts a list of chance/multiplier pairs from some place in the configuration. * It detects whether the configuration contains just a single multiplier, or if there are several chance/multipliers listed. * * @param conf * @param pathToMultiplier The configuration path prefix up to where the chance/value(s) are listed. * * @return null if no multiplier configuration was found at the given path. * Otherwise, a list of pairs of floats. Each pair is a chance (head) and multiplier (tail). * This is a sorted list of pairs of floats representing the multipliers to apply. * The pair's head is the chance (0.0 to 1.0) and tail is the multiplier (0.0 and above). * The list is sorted according to the pair's chance, pairs with higher chances coming first. */ private void readMultipliers(final Object[] properties, final ConfProps confProperty) { final String pathToMultiplier = this.confPathPrefix + "." + confProperty; final List<List<Float>> multipliers = new ArrayList<List<Float>>(); // A list of pairs of floats. Each pair is a chance (head) and multiplier (tail). // First extract the multiplier chance/value pairs from the configuration and add them all to the multipliers list. final Object multiplierProp = this.conf.getProperty(pathToMultiplier); if (multiplierProp instanceof Number) { addMultiplier(multipliers, 1.0F, Math.max(0.0F, (float)this.conf.getDouble(pathToMultiplier, 1.0D))); } else if (multiplierProp instanceof List<?>) { for(final Object multiplierListItemProp : (List<?>) multiplierProp) { if (multiplierListItemProp instanceof HashMap<?,?>) { final HashMap<?,?> chanceAndValueProp = (HashMap<?,?>) multiplierListItemProp; final Object chanceProp = chanceAndValueProp.get(CONF_MULTIPLIER_CHANCE.toString()); final Object valueProp = chanceAndValueProp.get(CONF_MULTIPLIER_VALUE.toString()); if ((chanceProp instanceof Double) && (valueProp instanceof Double)) { addMultiplier(multipliers, (float)Math.min(1.0D, Math.max(0.0D, ((Double)chanceProp).doubleValue())), (float)Math.max(0.0D, ((Double)valueProp).doubleValue())); } else { this.log.warning("HigherExplosives: Config problem. Ignoring list item under " + pathToMultiplier + " as either the " + CONF_MULTIPLIER_CHANCE + " of (" + chanceProp + ") or the " + CONF_MULTIPLIER_VALUE + " of (" + valueProp + ") doesn't look like a number. Was expecting a double."); } } else { this.log.warning("HigherExplosives: Config problem. Ignoring strange list item under " + pathToMultiplier + ". Was expecting " + CONF_MULTIPLIER_CHANCE + " and " + CONF_MULTIPLIER_VALUE + " keys"); } } // Make a check that the cumulative chance of all the chance/value pairs totals up to 1.0 float cumulativeChance = 0.0F; for(final List<Float> chanceValuePair : multipliers) { cumulativeChance += chanceValuePair.get(0); } if (Math.abs(cumulativeChance - 1.0F) > 0.0001D) { this.log.warning("HigherExplosives: Config problem. Total probability for " + pathToMultiplier + " doesn't quite " + "add up to 1.0. It's " + cumulativeChance); } } if (multipliers.size() == 0) { properties[confProperty.ordinal()] = null; } else { properties[confProperty.ordinal()] = multipliers; } } /** * Adds the chance and value as a new pair to the existing list of multpliers. * * @param multipliers The existing list of chance/value multiplier pairs * * @param chance The chance of this value being chosen. * @param value The multiplier value itself. */ private void addMultiplier(final List<List<Float>> multipliers, final float chance, final float value) { final List<Float> chanceValuePair = new ArrayList<Float>(2); chanceValuePair.add(chance); chanceValuePair.add(value); multipliers.add(chanceValuePair); // Comparator that places higher chances before lower chances final Comparator<List<Float>> chanceComparator = new Comparator<List<Float>>() { public int compare(List<Float> a, List<Float> b) { if (a.get(0) == b.get(0)) { return 0; } if (a.get(0) < b.get(0)) return 1; return -1; } }; Collections.sort(multipliers, chanceComparator); } }