From 9fe205ba08cef52bc0f17c9b14a153c96262d0b2 Mon Sep 17 00:00:00 2001 From: LemADEC Date: Sun, 17 Jan 2016 23:07:14 +0100 Subject: [PATCH] Integrating WorldGeneration (wip) Fixed fillerSet dependencies impacting UndergroundBiomes fillerSets Integrated XMLpreprocessor Refactored mod requirement handling Refactored RandomCollection Fixed OrbShell name overwriting Orb name Updated asteroid attributes: - minCoreSize -> minCoreCount - maxCoreSize -> maxCoreCount - coreRad -> relativeCoreRadius Updated default structures for stars and asteroids --- .../warpdrive/config/RandomCollection.java | 141 +++++++++ .../warpdrive/config/XmlPreprocessor.java | 291 ++++++++++-------- .../warpdrive/config/XmlRepresentable.java | 10 +- .../config/filler/FillerManager.java | 130 ++++---- .../warpdrive/config/filler/FillerSet.java | 82 ++--- .../warpdrive/config/structures/Asteroid.java | 213 +++++++------ .../structures/DeployableStructure.java | 36 +-- .../cr0s/warpdrive/config/structures/Orb.java | 226 ++++++-------- .../config/structures/Planetoid.java | 7 +- .../config/structures/SchematicStructure.java | 4 +- .../warpdrive/config/structures/Star.java | 24 +- .../config/structures/StructureManager.java | 230 ++++---------- .../cr0s/warpdrive/world/EntitySphereGen.java | 18 +- .../warpdrive/world/SpaceWorldGenerator.java | 32 +- src/main/resources/config/filler-default.xml | 14 +- .../resources/config/filler-netherores.xml | 2 +- .../config/filler-undergroundbiomes.xml | 6 +- .../resources/config/structures-default.xml | 97 +++++- 18 files changed, 837 insertions(+), 726 deletions(-) create mode 100644 src/main/java/cr0s/warpdrive/config/RandomCollection.java diff --git a/src/main/java/cr0s/warpdrive/config/RandomCollection.java b/src/main/java/cr0s/warpdrive/config/RandomCollection.java new file mode 100644 index 00000000..e3a2a974 --- /dev/null +++ b/src/main/java/cr0s/warpdrive/config/RandomCollection.java @@ -0,0 +1,141 @@ +package cr0s.warpdrive.config; + +import java.util.ArrayList; +import java.util.NavigableMap; +import java.util.Random; +import java.util.TreeMap; + +import org.w3c.dom.Element; + +import cr0s.warpdrive.WarpDrive; + +/** + * Collection of elements with weights. Helps to select element with controlled odds. + * + * @author ncrashed + * + * @param + */ +public class RandomCollection { + private final NavigableMap weightMap = new TreeMap(); + private double totalWeight = 0; + private final NavigableMap ratioMap = new TreeMap(); + private double totalRatio = 0; + private final ArrayList list = new ArrayList(); + + /** + * Add new object and its weight. + * + * @param weight + * Used for random pick. The higher the value is relatively to others, the higher odds of choosing the object. + * @param object + * Object to add + */ + public void addWeight(double weight, E object) { + if (weight <= 0) { + WarpDrive.logger.warn("Weight is negative or zero, skipping " + object); + return; + } + totalWeight += weight; + weightMap.put(totalWeight, object); + list.add(object); + } + + /** + * Add new object and its ratio. Warning: if total ratio goes higher than 1.0, element won't be added to collection. + * + * @param ratio + * Chance of random pick in range (0, 1.0]. In contrast to weights, ratio is fixed and chances don't change if you add more elements. + * @param object + * Object to add + */ + public void addRatio(double ratio, E object) { + if (ratio <= 0 || ratio >= 1.0) { + WarpDrive.logger.warn("Ratio isn't in (0, 1.0] bounds, skipping " + object); + return; + } + + if (totalRatio + ratio > 1.0) { + WarpDrive.logger.warn("Total ratio is greater than 1.0, skipping " + object); + return; + } + totalRatio += ratio; + ratioMap.put(totalRatio, object); + list.add(object); + } + + /** + * Get a random object according weights and ratios + * + * @param random + * @return Random object or null if there is no objects to pick. + */ + public E getRandomEntry(Random random) { + double value = random.nextDouble(); + + if (value < totalRatio) { // hit ratio part of values + return ratioMap.ceilingEntry(value).getValue(); + } else { // hit dynamic part of values, weighted ones + double weight = (value - totalRatio) * totalWeight; + return weightMap.ceilingEntry(weight).getValue(); + } + } + + /** + * Get a specific object through its name + * + * @param name Exact name of the object + * @return Named object or null if there is no object with that name + */ + public E getNamedEntry(final String name) { + for(E object : list) { + if (object.getName().equals(name)) { + return object; + } + } + return null; + } + + /** + * @return All registered objects + */ + public ArrayList elements() { + return list; + } + + /** + * Loads object from given XML element and parses configurations for weighted pick. + * + * @param object + * Object to load into + * @param element + * Element of an XML file + * @throws InvalidXmlException + */ + public void loadFromXML(E object, Element element) throws InvalidXmlException { + object.loadFromXmlElement(element); + + try { + String ratioStr = element.getAttribute("ratio"); + if (!ratioStr.isEmpty()) { + double ratio = Double.parseDouble(ratioStr); + addRatio(ratio, object); + } else { // try weight + try { + int weight = 1; + String stringWeight = element.getAttribute("weight"); + if (!stringWeight.isEmpty()) { + weight = Integer.parseInt(stringWeight); + weight = Math.max(1, weight); + } + + addWeight(weight, object); + } catch (NumberFormatException exceptionWeight) { + throw new InvalidXmlException("Weight must be an integer!"); + } + } + } catch (NumberFormatException exceptionRatio) { + throw new InvalidXmlException("Ratio must be double!"); + } + } +} \ No newline at end of file diff --git a/src/main/java/cr0s/warpdrive/config/XmlPreprocessor.java b/src/main/java/cr0s/warpdrive/config/XmlPreprocessor.java index 44f482b1..aa950936 100644 --- a/src/main/java/cr0s/warpdrive/config/XmlPreprocessor.java +++ b/src/main/java/cr0s/warpdrive/config/XmlPreprocessor.java @@ -1,9 +1,16 @@ package cr0s.warpdrive.config; +import java.io.File; import java.util.ArrayList; import java.util.Map.Entry; import java.util.TreeMap; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.stream.StreamResult; + import org.w3c.dom.Attr; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; @@ -13,44 +20,48 @@ import org.w3c.dom.NodeList; import cpw.mods.fml.common.Loader; import cr0s.warpdrive.WarpDrive; +import javax.xml.transform.dom.DOMSource; + public class XmlPreprocessor { + static boolean enableOuput = false; + static int outputCount = 1; /** - * Will check the given element for a mod attribute and return a string of all the ones that are not loaded, separated by commas + * Check the given element for a mod attribute and return a string of all the ones that are not loaded, separated by commas * * @param element * Element to check * @return A string, which is empty if all the mods are loaded. * @throws InvalidXmlException */ - public static ModCheckResults checkModRequirements(Element element) { + public static String checkModRequirements(Element element) { - ModCheckResults modErrors = new ModCheckResults(); + ModCheckResults modCheckResults = new ModCheckResults(); for (String mod : element.getAttribute("mods").split(",")) { //TODO: add version check - - if (mod.isEmpty()) + if (mod.isEmpty()) { continue; + } if (mod.startsWith("!")) { + if (Loader.isModLoaded(mod.substring(1))) { + modCheckResults.addMod(mod, "loaded"); + } - if (Loader.isModLoaded(mod.substring(1))) - modErrors.addMod(mod, "loaded"); - - } else if (!Loader.isModLoaded(mod)) - modErrors.addMod(mod, "not loaded"); - + } else if (!Loader.isModLoaded(mod)) { + modCheckResults.addMod(mod, "not loaded"); + } } - return modErrors; + return modCheckResults.toString(); } /** - * Goes through every child node of the given node, and if it is an element and fails checkModRequirements() it is removed + * Goes through every child node of the given node, and remove elements failing to checkModRequirements() * * @param base * @throws InvalidXmlException @@ -64,11 +75,11 @@ public class XmlPreprocessor { if (child instanceof Element) { Element elementChild = (Element) child; - ModCheckResults res = checkModRequirements(elementChild); - if (!res.isEmpty()) { + String result = checkModRequirements(elementChild); + if (!result.isEmpty()) { WarpDrive.logger.info("Skipping " + base.getNodeName() + "/" + elementChild.getNodeName() + " " + elementChild.getAttribute("group") + elementChild.getAttribute("name") + elementChild.getAttribute("block") - + " due to " + res); + + " due to " + result); base.removeChild(child); } else { doModReqSanitation(child); @@ -77,163 +88,171 @@ public class XmlPreprocessor { } } + /** + * Develop 'for' elements + * + * @param base + * @throws InvalidXmlException + */ public static void doLogicPreprocessing(Node root) throws InvalidXmlException { + // process child first NodeList children = root.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { doLogicPreprocessing(children.item(i)); } - - if (root.getNodeType() == Node.ELEMENT_NODE && ((Element) root).getTagName().equalsIgnoreCase("for")) { + + // only process 'for' elements + if (root.getNodeType() != Node.ELEMENT_NODE || !((Element) root).getTagName().equalsIgnoreCase("for")) { + return; + } + Element elementFor = (Element) root; + + // get variable name + String variableName = elementFor.getAttribute("variable"); + if(variableName.isEmpty()) { + throw new InvalidXmlException("A for tag must include a variable attribute!"); + } + + // 'in' takes precedence over 'from' attribute + if (elementFor.hasAttribute("in")) { + String[] inOptions = elementFor.getAttribute("in").split(","); - Element forTag = (Element) root; - - String varName = forTag.getAttribute("variable"); - if(varName.isEmpty()) - throw new InvalidXmlException("A for tag must include a variable attribute!"); - - //In supersedes from - if (forTag.hasAttribute("in")) { - String inOptions = forTag.getAttribute("in"); - - for(String input : inOptions.split(",")) { - - NodeList allChildren = root.getChildNodes(); - for(int chI = 0; chI < allChildren.getLength(); chI ++) { - - Node copy = getCopyVarReplace(allChildren.item(chI), varName, input); - root.getParentNode().appendChild(copy); - - } - + // copy children with replaced variable + for(String variableValue : inOptions) { + NodeList allChildren = root.getChildNodes(); + for(int childIndex = 0; childIndex < allChildren.getLength(); childIndex ++) { + Node copy = copyNodeAndReplaceVariable(allChildren.item(childIndex), variableName, variableValue); + root.getParentNode().appendChild(copy); } - - } else { - - String fromStr = forTag.getAttribute("from"); - String toStr = forTag.getAttribute("to"); + } + + } else { + String stringFrom = elementFor.getAttribute("from"); + String stringTo = elementFor.getAttribute("to"); + + if (stringTo.isEmpty() || stringFrom.isEmpty()) { + throw new InvalidXmlException("For element with no 'in' attribute requires both 'from' and 'to' attributes! " + variableName); + } + + int intFrom; + int intTo; + try { + intFrom = Integer.parseInt(stringFrom); + intTo = Integer.parseInt(stringTo); + } catch (NumberFormatException exception) { + throw new InvalidXmlException(exception); + } + + // copy children with replaced variable + for (int variableValue = intFrom; variableValue <= intTo; variableValue++) { + NodeList allChildren = root.getChildNodes(); + for (int childIndex = 0; childIndex < allChildren.getLength(); childIndex++) { + Node copy = copyNodeAndReplaceVariable(allChildren.item(childIndex), variableName, "" + variableValue); + root.getParentNode().appendChild(copy); + } + } + } + + //Remove the old node + root.getParentNode().removeChild(root); + + if (enableOuput) { + try { + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + Result output = new StreamResult(new File("output" + outputCount + ".xml")); + Source input = new DOMSource(root.getOwnerDocument()); - if (toStr.isEmpty() || fromStr.isEmpty()) - throw new InvalidXmlException("If a for doesnt have an in attr, it must have a from and to!"); - - int from, to; - try { - from = Integer.parseInt(fromStr); - to = Integer.parseInt(toStr); - } catch (NumberFormatException e) { - throw new InvalidXmlException(e); - } - - for (; from <= to; from++) { - - NodeList allChildren = root.getChildNodes(); - for (int chI = 0; chI < allChildren.getLength(); chI++) { - - Node copy = getCopyVarReplace(allChildren.item(chI), varName, "" + from); - root.getParentNode().appendChild(copy); - - } - - } - + transformer.transform(input, output); + outputCount++; + } catch (Exception exception) { + exception.printStackTrace(); } - - //Remove the old node - root.getParentNode().removeChild(root); - - } - } - - private static Node getCopyVarReplace(Node toCopy, String varName, String value) { - - Node copy = toCopy.cloneNode(true); - replaceVar(copy, varName, value); + + private static Node copyNodeAndReplaceVariable(Node nodeOriginal, String variableName, String variableValue) { + Node nodeCopy = nodeOriginal.cloneNode(true); + replaceVariable(nodeCopy, "%" + variableName + "%", variableValue); - return copy; + return nodeCopy; } - - private static void replaceVar(Node root, String varName, String value) { - - ArrayList toRemove = new ArrayList(); - ArrayList toAdd = new ArrayList(); - - if (root.getNodeType() == Node.ELEMENT_NODE) { + + private static void replaceVariable(Node node, String keyword, String value) { + ArrayList nameToRemove = new ArrayList(); + ArrayList attrToAdd = new ArrayList(); + + // process element's attributes first + if (node.getNodeType() == Node.ELEMENT_NODE) { - //First replace attributes - NamedNodeMap attrs = root.getAttributes(); - for (int i = 0; i < attrs.getLength(); i++) { - - Attr attr = (Attr) attrs.item(i); - String name = attr.getName(); - String newName = name.replace("%" + varName + "%", value); - - if (name.equals(newName)) { - - //Easy, just adjust value - attr.setValue(attr.getValue().replace("%" + varName + "%", value)); - - } else { - - //The name changed - toRemove.add(name); - - Attr newAttr = attr.getOwnerDocument().createAttribute(newName); - newAttr.setValue(attr.getValue().replace("%" + varName + "%", value)); - toAdd.add(newAttr); - + // compute the changes + NamedNodeMap attrs = node.getAttributes(); + for (int indexAttr = 0; indexAttr < attrs.getLength(); indexAttr++) { + Attr oldAttr = (Attr) attrs.item(indexAttr); + String oldName = oldAttr.getName(); + String newName = oldName.replace(keyword, value); + + if (oldName.equals(newName)) {// same name, just adjust the value + oldAttr.setValue(oldAttr.getValue().replace(keyword, value)); + + } else {// different name, needs to defer the add/remove + nameToRemove.add(oldName); + + Attr newAttr = oldAttr.getOwnerDocument().createAttribute(newName); + newAttr.setValue(oldAttr.getValue().replace(keyword, value)); + attrToAdd.add(newAttr); } - } - - //Now do the adds and removals - for (String attr : toRemove) + + // then apply them + for (String attr : nameToRemove) { attrs.removeNamedItem(attr); + } - for (Attr attr : toAdd) + for (Attr attr : attrToAdd) { attrs.setNamedItem(attr); - } - - //Now that Attributes are done, go through all of the children - - NodeList children = root.getChildNodes(); - for (int i = 0; i < children.getLength(); i++) { - Node child = children.item(i); - - switch (child.getNodeType()) { - case Node.ELEMENT_NODE://Recurse on the element - replaceVar(child, varName, value); - break; - case Node.TEXT_NODE: - child.setTextContent(child.getTextContent().replace("%" + varName + "%", value)); - break; - } } - + + // attributes are done, moving through child elements now + NodeList children = node.getChildNodes(); + for (int childIndex = 0; childIndex < children.getLength(); childIndex++) { + Node nodeChild = children.item(childIndex); + + switch (nodeChild.getNodeType()) { + case Node.ELEMENT_NODE: // recurse through elements + replaceVariable(nodeChild, keyword, value); + break; + case Node.TEXT_NODE: // replace text in place + nodeChild.setTextContent(nodeChild.getTextContent().replace(keyword, value)); + break; + default: // ignore others + // no operation + break; + } + } } public static class ModCheckResults { - private TreeMap mods; + private TreeMap modResults; public ModCheckResults() { - mods = new TreeMap(); + modResults = new TreeMap(); } public void addMod(String name, String error) { - mods.put(name, error); + modResults.put(name, error); } public boolean isEmpty() { - return mods.isEmpty(); + return modResults.isEmpty(); } @Override public String toString() { - String string = (mods.size() > 1 ? "{" : ""); + String string = (modResults.size() > 1 ? "{" : ""); boolean isFirst = true; - for (Entry entry : mods.entrySet()) { + for (Entry entry : modResults.entrySet()) { if (isFirst) { isFirst = false; } else { @@ -242,7 +261,7 @@ public class XmlPreprocessor { string += entry.getKey() + ": " + entry.getValue(); } - return string + (mods.size() > 1 ? "}" : ""); + return string + (modResults.size() > 1 ? "}" : ""); } } } diff --git a/src/main/java/cr0s/warpdrive/config/XmlRepresentable.java b/src/main/java/cr0s/warpdrive/config/XmlRepresentable.java index d12bce61..2324168e 100644 --- a/src/main/java/cr0s/warpdrive/config/XmlRepresentable.java +++ b/src/main/java/cr0s/warpdrive/config/XmlRepresentable.java @@ -4,9 +4,9 @@ import org.w3c.dom.Document; import org.w3c.dom.Element; public interface XmlRepresentable { - - public void loadFromXmlElement(Element e) throws InvalidXmlException; - - public void saveToXmlElement(Element e, Document d) throws InvalidXmlException; - + public String getName(); + + public void loadFromXmlElement(Element element) throws InvalidXmlException; + + public void saveToXmlElement(Element element, Document document) throws InvalidXmlException; } diff --git a/src/main/java/cr0s/warpdrive/config/filler/FillerManager.java b/src/main/java/cr0s/warpdrive/config/filler/FillerManager.java index 31362657..e3f836ae 100644 --- a/src/main/java/cr0s/warpdrive/config/filler/FillerManager.java +++ b/src/main/java/cr0s/warpdrive/config/filler/FillerManager.java @@ -15,16 +15,17 @@ import org.xml.sax.SAXException; import cr0s.warpdrive.WarpDrive; import cr0s.warpdrive.config.InvalidXmlException; +import cr0s.warpdrive.config.RandomCollection; import cr0s.warpdrive.config.WarpDriveConfig; import cr0s.warpdrive.config.XmlPreprocessor; -import cr0s.warpdrive.config.XmlPreprocessor.ModCheckResults; public class FillerManager { - private static TreeMap fillerSets = new TreeMap(); + private static TreeMap fillerSetsByName = new TreeMap(); + private static TreeMap> fillerSetsByGroup = new TreeMap>(); // Stores extra dependency information - static TreeMap> fillerSetsAdditions = new TreeMap>(); + static TreeMap> fillerSetsDependencies = new TreeMap>(); /* TODO dead code? // FillerSets that are guaranteed to exist @@ -52,7 +53,6 @@ public class FillerManager { for(File file : files) { try { - WarpDrive.logger.info("Loading filler data file " + file.getName() + "..."); loadXmlFillerFile(file); } catch (Exception exception) { WarpDrive.logger.error("Error loading filler data file " + file.getName() + ": " + exception.getMessage()); @@ -63,44 +63,57 @@ public class FillerManager { } private static void loadXmlFillerFile(File file) throws InvalidXmlException, SAXException, IOException { + WarpDrive.logger.info("Loading filler data file " + file.getName()); + Document document = WarpDriveConfig.getXmlDocumentBuilder().parse(file); - Document base = WarpDriveConfig.getXmlDocumentBuilder().parse(file); - - ModCheckResults res = XmlPreprocessor.checkModRequirements(base.getDocumentElement()); - - if (!res.isEmpty()) { - WarpDrive.logger.info("Skippping filler data file " + file.getName() + " due to " + res); + // pre-process the file + String result = XmlPreprocessor.checkModRequirements(document.getDocumentElement()); + if (!result.isEmpty()) { + WarpDrive.logger.info("Skipping filler data file " + file.getName() + " due to " + result); return; } - // Remove elements based on mod reqs sanitation - XmlPreprocessor.doModReqSanitation(base); - XmlPreprocessor.doLogicPreprocessing(base); + XmlPreprocessor.doModReqSanitation(document); + XmlPreprocessor.doLogicPreprocessing(document); - // Initially add FillerSets - NodeList nodesFillerSet = base.getElementsByTagName("FillerSet"); - for (int i = 0; i < nodesFillerSet.getLength(); i++) { + // only add FillerSets + NodeList nodesFillerSet = document.getElementsByTagName("FillerSet"); + for (int fillerSetIndex = 0; fillerSetIndex < nodesFillerSet.getLength(); fillerSetIndex++) { - Element elementFillerSet = (Element) nodesFillerSet.item(i); + Element elementFillerSet = (Element) nodesFillerSet.item(fillerSetIndex); String group = elementFillerSet.getAttribute("group"); if (group.isEmpty()) { - throw new InvalidXmlException("FillerSet " + i + " is missing a group attribute!"); + throw new InvalidXmlException("FillerSet " + (fillerSetIndex + 1) + "/" + nodesFillerSet.getLength() + " is missing a group attribute!"); } - FillerSet fillerSet = fillerSets.get(group); - if (fillerSet == null) { - fillerSet = new FillerSet(group); - fillerSets.put(group, fillerSet); + String name = elementFillerSet.getAttribute("name"); + if (name.isEmpty()) { + throw new InvalidXmlException("FillerSet " + (fillerSetIndex + 1) + "/" + nodesFillerSet.getLength() + " is missing a name attribute!"); } + WarpDrive.logger.info("- found FillerSet " + group + ":" + name); + + FillerSet fillerSet = fillerSetsByName.get(name); + if (fillerSet == null) { + fillerSet = new FillerSet(group, name); + fillerSetsByName.put(name, fillerSet); + } + + RandomCollection randomCollection = fillerSetsByGroup.get(group); + if (randomCollection == null) { + randomCollection = new RandomCollection(); + fillerSetsByGroup.put(group, randomCollection); + } + randomCollection.loadFromXML(fillerSet, elementFillerSet); + if (elementFillerSet.hasAttribute("fillerSets")) { - ArrayList setUnresolvedDeps = fillerSetsAdditions.get(fillerSet); - if (setUnresolvedDeps == null) { - setUnresolvedDeps = new ArrayList(); - fillerSetsAdditions.put(fillerSet, setUnresolvedDeps); + ArrayList dependencies = fillerSetsDependencies.get(fillerSet); + if (dependencies == null) { + dependencies = new ArrayList(); + fillerSetsDependencies.put(fillerSet, dependencies); } - setUnresolvedDeps.addAll(Arrays.asList(elementFillerSet.getAttribute("import").split(","))); + dependencies.addAll(Arrays.asList(elementFillerSet.getAttribute("fillerSets").split(","))); } fillerSet.loadFromXmlElement(elementFillerSet); @@ -108,47 +121,48 @@ public class FillerManager { } public static void finishLoading() { + // import fillerSets into each others + propagateFillerSets(); - while (!fillerSetsAdditions.isEmpty()) { - attemptDependencyFilling(fillerSetsAdditions); - } - - // When everything is done, finalize - for (FillerSet fillerSet : fillerSets.values()) { + // compute fillerSets randomization tables + for (FillerSet fillerSet : fillerSetsByName.values()) { fillerSet.finishContruction(); } + + // compute groups randomization tables + for (RandomCollection randomCollection : fillerSetsByGroup.values()) { + // randomCollection.finishContruction(); + } } - private static void attemptDependencyFilling(TreeMap> fillerSetsDeps) { - - ArrayList toRemove = new ArrayList(); - - for (Entry> entry : fillerSetsDeps.entrySet()) { + private static void propagateFillerSets() { + while (!fillerSetsDependencies.isEmpty()) { + TreeMap> fillerSetsLeftToImport = new TreeMap>(); - for (String dep : entry.getValue()) { - - if (!fillerSets.containsKey(dep)) { - - WarpDrive.logger.error("Skipping FillerSet " + entry.getKey() + " due to missing dependency " + dep); - fillerSets.remove(entry.getKey().getName()); - toRemove.add(entry.getKey()); - - } else if (fillerSetsDeps.containsKey(fillerSets.get(dep))) { - //Skip until it is loaded - } else { - - entry.getKey().loadFrom(fillerSets.get(dep)); - toRemove.add(entry.getKey()); + for (Entry> entry : fillerSetsDependencies.entrySet()) { + ArrayList newDependencies = new ArrayList(); + for (String dependency : entry.getValue()) { + if (!fillerSetsByName.containsKey(dependency)) { + WarpDrive.logger.error("Ignoring FillerSet " + dependency + " dependency in FillerSet " + entry.getKey()); + + } else if (fillerSetsDependencies.containsKey(fillerSetsByName.get(dependency))) { + // skip until it is loaded + newDependencies.add(dependency); + + } else { + entry.getKey().loadFrom(fillerSetsByName.get(dependency)); + } + } + if (!newDependencies.isEmpty()) { + fillerSetsLeftToImport.put(entry.getKey(), newDependencies); } } - } - - for (FillerSet set : toRemove) { - fillerSetsDeps.remove(set); + + fillerSetsDependencies = fillerSetsLeftToImport; } } - public static FillerSet getFillerSet(String name) { - return fillerSets.get(name); + public static boolean doesFillerSetExist(String groupOrName) { + return fillerSetsByName.containsKey(groupOrName) || fillerSetsByName.containsKey(groupOrName); } } diff --git a/src/main/java/cr0s/warpdrive/config/filler/FillerSet.java b/src/main/java/cr0s/warpdrive/config/filler/FillerSet.java index 2a53d701..2eb713a5 100644 --- a/src/main/java/cr0s/warpdrive/config/filler/FillerSet.java +++ b/src/main/java/cr0s/warpdrive/config/filler/FillerSet.java @@ -20,51 +20,57 @@ import net.minecraft.init.Blocks; * */ public class FillerSet implements XmlRepresentable, Comparable { - private MetaBlock[] weightedFillerBlocks; private FillerFactory factory; + protected String group; protected String name; - + + public String getGroup() { + return group; + } + + @Override public String getName() { return name; } - + public FillerSet(MetaBlock[] blocks) { weightedFillerBlocks = blocks; } - - public FillerSet(String name) { - + + public FillerSet(final String group, final String name) { + + this.group = group; this.name = name; - + weightedFillerBlocks = new MetaBlock[1]; factory = new FillerFactory(); } - + public MetaBlock getRandomBlock(Random rand) { return weightedFillerBlocks[rand.nextInt(weightedFillerBlocks.length)]; } - + @Override public void loadFromXmlElement(Element element) throws InvalidXmlException { - + NodeList fillers = element.getElementsByTagName("filler"); for (int i = 0; i < fillers.getLength(); i++) { - + Element filler = (Element) fillers.item(i); - + // Check there is a block name if (!filler.hasAttribute("block")) { throw new InvalidXmlException("Filler " + filler + " is missing a block tag!"); } - + String blockName = filler.getAttribute("block"); Block block = Block.getBlockFromName(blockName); if (block == null) { WarpDrive.logger.warn("Skipping missing block " + blockName); continue; } - + // Get metadata attribute, defaults to 0 int intMetadata = 0; String stringMetadata = filler.getAttribute("metadata"); @@ -75,54 +81,58 @@ public class FillerSet implements XmlRepresentable, Comparable { throw new InvalidXmlException("Invalid metadata for block " + blockName); } } - + boolean hasWeightOrRatio = false; - + // It is intentional that a filler could have both a ratio and a weight - + // Check for a weight and add it to the factory String stringWeight = filler.getAttribute("weight"); int weight; - + if (!stringWeight.isEmpty()) { hasWeightOrRatio = true; - + try { weight = Integer.parseInt(stringWeight); - + factory.addWeightedBlock(block, intMetadata, weight); - + } catch (NumberFormatException exception) { throw new InvalidXmlException("Invalid weight for block " + blockName); } catch (IllegalArgumentException exception) { throw new InvalidXmlException(exception.getMessage()); } } - + // Check for a ratio attribute, and add it to the factory String stringRatio = filler.getAttribute("ratio"); if (!stringRatio.isEmpty()) { hasWeightOrRatio = true; - + try { factory.addRatioBlock(block, intMetadata, stringRatio); - + } catch (IllegalArgumentException exception) { throw new InvalidXmlException(exception.getMessage()); } } - + if (!hasWeightOrRatio) { throw new InvalidXmlException("No ratio nor weight defined for block " + blockName + " " + stringMetadata); } } } - + + /** + * @deprecated Not implemented + **/ + @Deprecated @Override public void saveToXmlElement(Element element, Document document) throws InvalidXmlException { - throw new InvalidXmlException("Not supported"); + throw new InvalidXmlException("Not implemented"); } - + /** * Uses the data that has been loaded thus far to construct the array in order to make the FillerSet functional. Must be called before calling getRandomBlock() * @@ -131,27 +141,27 @@ public class FillerSet implements XmlRepresentable, Comparable { public void finishContruction() { WarpDrive.logger.info("Finishing construction of " + name); weightedFillerBlocks = factory.constructWeightedMetaBlockList(); - - //For some reason some entries are null, so replace them with air + + //For some reason some entries are null, so replace them with air FIXME for (int i = 0; i < weightedFillerBlocks.length; i++) { - if (weightedFillerBlocks[i] == null) + if (weightedFillerBlocks[i] == null) { weightedFillerBlocks[i] = MetaBlock.getMetaBlock(Blocks.air, 0); - + } } - + factory = null; } - + @Override public int compareTo(Object object) { return name.compareTo(((FillerSet) object).name); } - + @Override public String toString() { return name; } - + /** * Adds the blocks from the given fillerSet into this one. Must be pre-finishConstruction() * diff --git a/src/main/java/cr0s/warpdrive/config/structures/Asteroid.java b/src/main/java/cr0s/warpdrive/config/structures/Asteroid.java index d21866d0..c322c449 100644 --- a/src/main/java/cr0s/warpdrive/config/structures/Asteroid.java +++ b/src/main/java/cr0s/warpdrive/config/structures/Asteroid.java @@ -8,128 +8,132 @@ import org.w3c.dom.Element; import cr0s.warpdrive.WarpDrive; import cr0s.warpdrive.config.InvalidXmlException; import cr0s.warpdrive.config.MetaBlock; +import cr0s.warpdrive.data.VectorI; import net.minecraft.block.Block; import net.minecraft.init.Blocks; import net.minecraft.world.World; public class Asteroid extends Orb { - - private static final int MIN_RADIUS = 1; private static final int CORE_MAX_TRIES = 10; private Block coreBlock; - - private int maxCoreSize, minCoreSize; - private double coreRad; - public Asteroid() { - super(0); //Diameter not relevant + private int maxCoreCount; + private int minCoreCount; + private double relativeCoreRadius; + + public Asteroid(final String name) { + super(name); } - + @Override - public void loadFromXmlElement(Element e) throws InvalidXmlException { - - super.loadFromXmlElement(e); - - String coreBlockName = e.getAttribute("coreBlock"); - if (coreBlockName.isEmpty()) - throw new InvalidXmlException("Asteroid is missing a coreBlock!"); - + public void loadFromXmlElement(Element element) throws InvalidXmlException { + super.loadFromXmlElement(element); + + String coreBlockName = element.getAttribute("coreBlock"); + if (coreBlockName.isEmpty()) { + throw new InvalidXmlException("Asteroid " + name + " is missing a coreBlock!"); + } + coreBlock = Block.getBlockFromName(coreBlockName); - if (coreBlock == null) - throw new InvalidXmlException("Asteroid coreBlock doesnt exist!"); - - try { - - maxCoreSize = Integer.parseInt(e.getAttribute("maxCoreSize")); - minCoreSize = Integer.parseInt(e.getAttribute("minCoreSize")); - - } catch (NumberFormatException gdbg) { - throw new InvalidXmlException("Asteroid core size dimensions are NaN!"); + if (coreBlock == null) { + throw new InvalidXmlException("Asteroid " + name + " has an invalid/missing coreBlock " + coreBlockName); } try { - String coreRadStr = e.getAttribute("coreRad"); - if(coreRadStr.isEmpty()) { - coreRad = 0.1; + minCoreCount = Integer.parseInt(element.getAttribute("minCoreCount")); + } catch (NumberFormatException exception) { + throw new InvalidXmlException("Asteroid " + name + " has an invalid minCoreCount " + element.getAttribute("minCoreCount") + ", expecting an integer"); + } + + if (minCoreCount < 1) { + throw new InvalidXmlException("Asteroid " + name + " has an invalid minCoreCount " + minCoreCount + ", expecting greater then 0"); + } + + try { + maxCoreCount = Integer.parseInt(element.getAttribute("maxCoreCount")); + } catch (NumberFormatException exception) { + throw new InvalidXmlException("Asteroid " + name + " has an invalid maxCoreCount " + element.getAttribute("maxCoreCount") + ", expecting an integer"); + } + + if (maxCoreCount < minCoreCount) { + throw new InvalidXmlException("Asteroid " + name + " has an invalid maxCoreCount " + maxCoreCount + ", expecting greater than or equal to minCoreCount " + minCoreCount); + } + + try { + String stringCoreRad = element.getAttribute("relativeCoreRadius"); + if (stringCoreRad.isEmpty()) { + relativeCoreRadius = 0.1; } else { - coreRad = Double.parseDouble(e.getAttribute("coreRad")); + relativeCoreRadius = Double.parseDouble(element.getAttribute("relativeCoreRadius")); } } catch (NumberFormatException gdbg) { - throw new InvalidXmlException("Asteroid core rad must be double!"); + throw new InvalidXmlException("Asteroid " + name + " has an invalid relativeCoreRadius " + element.getAttribute("relativeCoreRadius") + ", expecting a double"); + } + + if (relativeCoreRadius < 0.0D || relativeCoreRadius > 1.0D) { + throw new InvalidXmlException("Asteroid " + name + " has an invalid relativeCoreRadius " + relativeCoreRadius + ", expecting a value between 0.0 and 1.0 included"); } - } @Override - public boolean generate(World world, Random rand, int x, int y, int z) { - int randRadius = MIN_RADIUS + rand.nextInt(Math.max(1, getRadius() - MIN_RADIUS)); - int numberCoreBlocks = minCoreSize + rand.nextInt(Math.max(1, maxCoreSize - minCoreSize)); - - WarpDrive.logger.info("Asteroid generation: radius=" + randRadius + ", numCoreBlocks=" + numberCoreBlocks + ", coreRad=" + coreRad); - - //Use this to generate a abstract form for the core. - ArrayList coreLocations = generateCore(world, rand, x, y, z, numberCoreBlocks, coreBlock, numberCoreBlocks, randRadius); - - for (Location coreLocation: coreLocations) { - // Calculate mininum distance to borders of generation area - int maxRadX = Math.min(x+randRadius-coreLocation.x, coreLocation.x - (x - randRadius)); - int maxRadY = Math.min(y+randRadius-coreLocation.y, coreLocation.y - (y - randRadius)); - int maxRadZ = Math.min(z+randRadius-coreLocation.z, coreLocation.z - (z - randRadius)); - int maxLocalRadius = Math.min(maxRadX, Math.min(maxRadY, maxRadZ)); - - // Generate shell - addShell(world, rand, coreLocation, maxLocalRadius); + public boolean generate(World world, Random random, int x, int y, int z) { + int[] thicknesses = randomize(random); + int totalThickness = 0; + for (int thickness : thicknesses) { + totalThickness += thickness; } - + int coreBlocksCount = minCoreCount + ((maxCoreCount > minCoreCount) ? random.nextInt(maxCoreCount - minCoreCount) : 0); + + WarpDrive.logger.info("Generating asteroid " + name + " as radius " + totalThickness + " coreBlocksCount " + coreBlocksCount + " coreRad " + relativeCoreRadius); + + // use this to generate an abstract form for the core. + double coreRadius = relativeCoreRadius * totalThickness; + ArrayList coreLocations = generateCore(world, random, x, y, z, coreBlocksCount, coreBlock, coreBlocksCount, coreRadius); + + for (VectorI coreLocation: coreLocations) { + // Calculate minimum distance to borders of generation area + int maxRadX = totalThickness - Math.abs(x - coreLocation.x); + int maxRadY = totalThickness - Math.abs(y - coreLocation.y); + int maxRadZ = totalThickness - Math.abs(z - coreLocation.z); + int maxLocalRadius = Math.max(maxRadX, Math.max(maxRadY, maxRadZ)); + + // Generate shell + addShell(thicknesses, world, coreLocation, maxLocalRadius); + } + return true; } - + /** * Creates a shell sphere around given core location. * + * @param thicknesses Random generator * @param world World to place shell - * @param rand Random generator - * @param l Location of core block + * @param location Location of core block * @param maxRad Maximum radius of asteroid */ - private void addShell(World world, Random rand, Location l, int maxRad) { - //int rad = MIN_RADIUS + rand.nextInt(Math.max(1, maxRad - MIN_RADIUS)); - int rad = maxRad; - - // Iterate all blocks withing cube with side 2*rad - for(int x = l.x - rad; x <= l.x + rad; ++x) { - for(int y = l.y - rad; y <= l.y + rad; ++y) { - for(int z = l.z - rad; z <= l.z + rad; ++z) { + private void addShell(int[] thicknesses, World world, VectorI location, int radius) { + // iterate all blocks within cube with side 2 * radius + for(int x = location.x - radius; x <= location.x + radius; x++) { + int dX2 = (x - location.x) * (x - location.x); + for(int y = location.y - radius; y <= location.y + radius; y++) { + int dX2Y2 = dX2 + (y - location.y) * (y - location.y); + for(int z = location.z - radius; z <= location.z + radius; z++) { // current radius - int r = (int)Math.round(Math.sqrt((l.x - x)*(l.x - x) + (l.y - y)*(l.y - y) + (l.z - z)*(l.z - z))); + int range = (int)Math.round(Math.sqrt(dX2Y2 + (location.z - z) * (location.z - z))); + // if inside radius - if(r <= rad && isBlockEmpty(world, x, y, z)) { - OrbShell shell = getShellForRadius(r); - MetaBlock blType = shell.getRandomBlock(rand); - world.setBlock(x, y, z, blType.block, blType.metadata, 0); + if(range <= radius && isReplaceableOreGen(world, x, y, z)) { + OrbShell shell = getShellForRadius(thicknesses, range); + MetaBlock metaBlock = shell.getRandomBlock(world.rand); + world.setBlock(x, y, z, metaBlock.block, metaBlock.metadata, 0); } } } } } - - /** - * Represents a single point in space - * - */ - private class Location { - - public int x, y, z; - - public Location(int x, int y, int z) { - this.x = x; - this.y = y; - this.z = z; - } - - } - + /** * Checks if given coordinate empty (air in terms of MC). * @param world @@ -138,7 +142,7 @@ public class Asteroid extends Orb { * @param z * @return */ - private static boolean isBlockEmpty(World world, int x, int y, int z) { + private static boolean isReplaceableOreGen(World world, int x, int y, int z) { return world.getBlock(x, y, z).isReplaceableOreGen(world, x, y, z, Blocks.air); } @@ -155,41 +159,36 @@ public class Asteroid extends Orb { * @param numberOfBlocks - number of core blocks to place * @param block - type of block to place * @param metadata - metadata of bloeck to place - * @param maxRange - max radius of asteroid + * @param coreRadius - max radius of asteroid * @return List of placed locations of cores */ - private ArrayList generateCore(World world, Random rand, int x, int y, int z, int numberOfBlocks, Block block, int metadata, - int maxRange) { - - ArrayList addedBlocks = new ArrayList(); - int coreRange = (int)Math.round(coreRad * maxRange); - int maxX = x + coreRange; - int minX = x - coreRange; - int maxY = y + coreRange; - int minY = y - coreRange; - int maxZ = z + coreRange; - int minZ = z - coreRange; + private ArrayList generateCore(World world, Random rand, int x, int y, int z, + int numberOfBlocks, Block block, int metadata, double coreRadius) { + ArrayList addedBlocks = new ArrayList(); + int coreDiameter = Math.max(1, (int)Math.round(2 * coreRadius)); + int xMin = x - (int)Math.round(coreRadius); + int yMin = y - (int)Math.round(coreRadius); + int zMin = z - (int)Math.round(coreRadius); - for (int i = 0; i < numberOfBlocks; ++i) { + for (int coreBlockIndex = 0; coreBlockIndex < numberOfBlocks; coreBlockIndex++) { int curX = x; int curY = y; int curZ = z; - boolean stopWalk = false; + boolean found = false; - for(int step = 0; step <= CORE_MAX_TRIES && !stopWalk; ++step) { - curX = rand.nextInt(Math.max(1, maxX - minX)) + minX; - curY = rand.nextInt(Math.max(1, maxY - minY)) + minY; - curZ = rand.nextInt(Math.max(1, maxZ - minZ)) + minZ; + for(int step = 0; step < CORE_MAX_TRIES && !found; step++) { + curX = xMin + rand.nextInt(coreDiameter); + curY = yMin + rand.nextInt(coreDiameter); + curZ = zMin + rand.nextInt(coreDiameter); - if (isBlockEmpty(world, curX, curY, curZ)) { + if (isReplaceableOreGen(world, curX, curY, curZ)) { world.setBlock(curX, curY, curZ, block, metadata, 2); - addedBlocks.add(new Location(curX, curY, curZ)); - stopWalk = true; + addedBlocks.add(new VectorI(curX, curY, curZ)); + found = true; } } } return addedBlocks; } - } diff --git a/src/main/java/cr0s/warpdrive/config/structures/DeployableStructure.java b/src/main/java/cr0s/warpdrive/config/structures/DeployableStructure.java index 6cb94f35..b57782ad 100644 --- a/src/main/java/cr0s/warpdrive/config/structures/DeployableStructure.java +++ b/src/main/java/cr0s/warpdrive/config/structures/DeployableStructure.java @@ -6,31 +6,29 @@ package cr0s.warpdrive.config.structures; import net.minecraft.world.gen.feature.WorldGenerator; /** - * @author Francesco + * @author Francesco, LemADEC * */ public abstract class DeployableStructure extends WorldGenerator { + protected String name; + protected int sizeX; + protected int sizeY; + protected int sizeZ; - protected int height; - protected int width; - protected int length; - - public DeployableStructure(int height, int width, int length) { - this.height = height; - this.width = width; - this.length = length; + public DeployableStructure(final String name) { + this.name = name; } - - public int getHeight() { - return height; + + public void setDimensions(final int sizeX, final int sizeY, final int sizeZ) { + this.sizeX = sizeX; + this.sizeY = sizeY; + this.sizeZ = sizeZ; } - - public int getWidth() { - return width; - } - - public int getLength() { - return length; + + public void setRadius(final int radius) { + sizeX = radius * 2; + sizeY = radius * 2; + sizeZ = radius * 2; } } diff --git a/src/main/java/cr0s/warpdrive/config/structures/Orb.java b/src/main/java/cr0s/warpdrive/config/structures/Orb.java index 460d1500..107f3811 100644 --- a/src/main/java/cr0s/warpdrive/config/structures/Orb.java +++ b/src/main/java/cr0s/warpdrive/config/structures/Orb.java @@ -15,164 +15,142 @@ import cr0s.warpdrive.config.XmlRepresentable; import cr0s.warpdrive.config.filler.FillerManager; import cr0s.warpdrive.config.filler.FillerSet; import cr0s.warpdrive.world.EntitySphereGen; +import cr0s.warpdrive.world.EntityStarCore; public abstract class Orb extends DeployableStructure implements XmlRepresentable { - - private OrbShell[] shellRelative; - // private ArrayList shells; - private String name; - - /** - * @return the radius - */ - public int getRadius() { - return super.height / 2; + + private OrbShell[] orbShells; + protected boolean hasStarCore = false; + private ArrayList fillerSetGroupOrNames = new ArrayList(); + + public Orb(final String name) { + super(name); } - - /** - * @param radius the radius to set - */ - public void setRadius(int radius) { - - super.height = radius * 2; - super.length = radius * 2; - super.width = radius * 2; - } - + + @Override public String getName() { return name; } - - public void setName(String name) { - this.name = name; - } - - public Orb(int radius) { - super(radius * 2, radius * 2, radius * 2); - - setRadius(radius); - - } - + @Override public void loadFromXmlElement(Element element) throws InvalidXmlException { - - name = element.getAttribute("name"); - - ArrayList newShells = new ArrayList(); - int totalThickness = 0; - - NodeList shells = element.getElementsByTagName("shell"); - for (int i = 0; i < shells.getLength(); i++) { - Element elementShell = (Element) shells.item(i); - - OrbShell shell = new OrbShell(); - shell.loadFromXmlElement(elementShell); - shell.finishContruction(); - totalThickness += shell.thickness; - newShells.add(shell); + + int maxThickness = 0; + + NodeList nodeListShells = element.getElementsByTagName("shell"); + orbShells = new OrbShell[nodeListShells.getLength()]; + for (int shellIndex = 0; shellIndex < nodeListShells.getLength(); shellIndex++) { + Element elementShell = (Element) nodeListShells.item(shellIndex); + String orbShellName = element.getAttribute("name"); + + orbShells[shellIndex] = new OrbShell(name, orbShellName); + orbShells[shellIndex].loadFromXmlElement(elementShell); + orbShells[shellIndex].finishContruction(); + maxThickness += orbShells[shellIndex].maxThickness; } - - int index = 0; - shellRelative = new OrbShell[totalThickness]; - - for (OrbShell shell : newShells) { - - for (int i = 0; i < shell.thickness; i++) - shellRelative[index++] = shell; - } - - setRadius(totalThickness - 1); + + setRadius(maxThickness - 1); } - + + /** + * @deprecated Not implemented + **/ + @Deprecated @Override - public void saveToXmlElement(Element element, Document document) { - /* TODO: dead code? - for (OrbShell shell : shells) { - Element tmp = document.createElement("shell"); - shell.saveToXmlElement(tmp, document); - element.appendChild(tmp); - } - /**/ + public void saveToXmlElement(Element element, Document document) throws InvalidXmlException { + throw new InvalidXmlException("Not implemented"); } - + @Override public boolean generate(World world, Random random, int x, int y, int z) { - EntitySphereGen entitySphereGen = new EntitySphereGen(world, x, y, z, getRadius(), this, true); + int[] thicknesses = randomize(random); + int totalThickness = 0; + for (int thickness : thicknesses) { + totalThickness += thickness; + } + EntitySphereGen entitySphereGen = new EntitySphereGen(world, x, y, z, this, thicknesses, totalThickness, true); world.spawnEntityInWorld(entitySphereGen); + if (hasStarCore) { + return world.spawnEntityInWorld(new EntityStarCore(world, x, y, z, totalThickness)); + } return false; } - + + /** + * + * @Deprecated pending addition of variables offsets in structure XML attributes + */ + @Deprecated public boolean generate(World world, Random random, int x, int y, int z, final int radius) { - EntitySphereGen entitySphereGen = new EntitySphereGen(world, x, y, z, radius, this, true); + EntitySphereGen entitySphereGen = new EntitySphereGen(world, x, y, z, this, null, radius, true); world.spawnEntityInWorld(entitySphereGen); return false; } - - public OrbShell getShellForRadius(int r) { - return shellRelative[r]; + + public int[] randomize(Random random) { + int[] thicknesses = new int[orbShells.length]; + for(int orbShellIndex = 0; orbShellIndex < orbShells.length; orbShellIndex++) { + OrbShell orbShell = orbShells[orbShellIndex]; + thicknesses[orbShellIndex] = orbShell.minThickness + + ((orbShell.maxThickness - orbShell.minThickness > 0) ? random.nextInt(orbShell.maxThickness - orbShell.minThickness) : 0); + } + return thicknesses; } - + + public OrbShell getShellForRadius(final int[] thicknesses, final int range) { + int cumulatedRange = 0; + for (int shellIndex = 0; shellIndex < orbShells.length; shellIndex++) { + cumulatedRange += thicknesses[shellIndex]; + if (range <= cumulatedRange) { + return orbShells[shellIndex]; + } + } + return null; + } + public class OrbShell extends FillerSet { - - private int thickness; - - /** - * @return the thickness - */ - public int getThickness() { - return thickness; + private String parentName; + private int minThickness; + private int maxThickness; + + public OrbShell(String parentName, String name) { + super(null, name); + this.parentName = parentName; } - - /** - * @param thickness - * the thickness to set - */ - public void setThickness(int thickness) { - this.thickness = thickness; - } - - public OrbShell() { - - super(""); - } - + @Override public void loadFromXmlElement(Element element) throws InvalidXmlException { - WarpDrive.logger.info("Loading shell " + element.getAttribute("name")); - name = element.getAttribute("name"); - + super.loadFromXmlElement(element); - + if (element.hasAttribute("fillerSets")) { - String[] imports = element.getAttribute("fillerSets").split(","); - - for (String imp : imports) { - FillerSet fillSet = FillerManager.getFillerSet(imp); - - if (fillSet == null) - throw new InvalidXmlException("Shell loading tries to import a non-existant fillerSet!"); - - super.loadFrom(fillSet); + String[] allFillerSetGroupOrNames = element.getAttribute("fillerSets").split(","); + + for (String fillerSetGroupOrName : allFillerSetGroupOrNames) { + if (!FillerManager.doesFillerSetExist(fillerSetGroupOrName)) { + WarpDrive.logger.warn("Skipping missing FillerSet " + fillerSetGroupOrName + " in shell " + name); + } else { + fillerSetGroupOrNames.add(fillerSetGroupOrName); + } } } - + try { - thickness = Integer.parseInt(element.getAttribute("maxThickness")); + minThickness = Integer.parseInt(element.getAttribute("minThickness")); } catch (NumberFormatException ex) { - throw new InvalidXmlException("MaxThickness is not valid!"); + throw new InvalidXmlException("Invalid minThickness in shell " + name + " of orb " + parentName); + } + + try { + maxThickness = Integer.parseInt(element.getAttribute("maxThickness")); + } catch (NumberFormatException ex) { + throw new InvalidXmlException("Invalid maxThickness in shell " + name + " of orb " + parentName); + } + + if (maxThickness < minThickness) { + throw new InvalidXmlException("Invalid maxThickness " + maxThickness + " lower than minThickness " + minThickness + " in shell " + name + " of orb " + parentName); } - - //TODO: Implement random thickness - } - - @Override - public void saveToXmlElement(Element e, Document d) { - //Not needed - } - } - } diff --git a/src/main/java/cr0s/warpdrive/config/structures/Planetoid.java b/src/main/java/cr0s/warpdrive/config/structures/Planetoid.java index f159625a..e055bf27 100644 --- a/src/main/java/cr0s/warpdrive/config/structures/Planetoid.java +++ b/src/main/java/cr0s/warpdrive/config/structures/Planetoid.java @@ -1,10 +1,7 @@ package cr0s.warpdrive.config.structures; public class Planetoid extends Orb { - - public Planetoid(int diameter) { - super(diameter); - // TODO Auto-generated constructor stub + public Planetoid(final String name) { + super(name); } - } diff --git a/src/main/java/cr0s/warpdrive/config/structures/SchematicStructure.java b/src/main/java/cr0s/warpdrive/config/structures/SchematicStructure.java index 018654f7..4da3b5dc 100644 --- a/src/main/java/cr0s/warpdrive/config/structures/SchematicStructure.java +++ b/src/main/java/cr0s/warpdrive/config/structures/SchematicStructure.java @@ -6,8 +6,8 @@ import net.minecraft.world.World; public class SchematicStructure extends DeployableStructure { - public SchematicStructure(int height, int width, int length) { - super(height, width, length); + public SchematicStructure(final String name) { + super(name); // TODO Auto-generated constructor stub } diff --git a/src/main/java/cr0s/warpdrive/config/structures/Star.java b/src/main/java/cr0s/warpdrive/config/structures/Star.java index 8f7e3978..90121a2d 100644 --- a/src/main/java/cr0s/warpdrive/config/structures/Star.java +++ b/src/main/java/cr0s/warpdrive/config/structures/Star.java @@ -1,27 +1,9 @@ package cr0s.warpdrive.config.structures; -import java.util.Random; - -import net.minecraft.world.World; -import cr0s.warpdrive.world.EntityStarCore; - public class Star extends Orb { - public Star(int diameter) { - super(diameter); + public Star(final String name) { + super(name); + hasStarCore = true; } - - @Override - public boolean generate(World p_76484_1_, Random p_76484_2_, int p_76484_3_, int p_76484_4_, int p_76484_5_) { - boolean success = super.generate(p_76484_1_, p_76484_2_, p_76484_3_, p_76484_4_, p_76484_5_); - - if (success) - return placeStarCore(p_76484_1_, p_76484_3_, p_76484_4_, p_76484_5_, super.getHeight() / 2); - return false; - } - - public static boolean placeStarCore(World world, int x, int y, int z, int radius) { - return world.spawnEntityInWorld(new EntityStarCore(world, x, y, z, radius)); - } - } diff --git a/src/main/java/cr0s/warpdrive/config/structures/StructureManager.java b/src/main/java/cr0s/warpdrive/config/structures/StructureManager.java index be4f8b27..20f0a270 100644 --- a/src/main/java/cr0s/warpdrive/config/structures/StructureManager.java +++ b/src/main/java/cr0s/warpdrive/config/structures/StructureManager.java @@ -3,10 +3,7 @@ package cr0s.warpdrive.config.structures; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; -import java.util.ArrayList; -import java.util.NavigableMap; import java.util.Random; -import java.util.TreeMap; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -15,27 +12,25 @@ import org.xml.sax.SAXException; import cr0s.warpdrive.WarpDrive; import cr0s.warpdrive.config.InvalidXmlException; +import cr0s.warpdrive.config.RandomCollection; import cr0s.warpdrive.config.WarpDriveConfig; import cr0s.warpdrive.config.XmlPreprocessor; -import cr0s.warpdrive.config.XmlPreprocessor.ModCheckResults; -import cr0s.warpdrive.config.XmlRepresentable; public class StructureManager { + public static final String GROUP_STARS = "star"; + public static final String GROUP_MOONS = "moon"; + public static final String GROUP_GASCLOUDS = "gascloud"; + public static final String GROUP_ASTEROIDS = "asteroid"; private static RandomCollection stars = new RandomCollection(); private static RandomCollection moons = new RandomCollection(); - private static RandomCollection gasClouds = new RandomCollection(); + private static RandomCollection gasClouds = new RandomCollection(); private static RandomCollection asteroids = new RandomCollection(); - public static void loadStructures(String structureConfDir) { - loadStructures(new File(structureConfDir)); - } - public static void loadStructures(File dir) { dir.mkdir(); - if (!dir.isDirectory()) { throw new IllegalArgumentException("File path " + dir.getPath() + " must be a directory!"); } @@ -49,13 +44,7 @@ public class StructureManager { for (File file : files) { try { - - WarpDrive.logger.info("Loading structure data file " + file.getName()); - loadXmlStructureFile(file); - - WarpDrive.logger.info("Finished loading structure data file " + file.getName()); - } catch (Exception exception) { WarpDrive.logger.error("Error loading file " + file.getName() + ": " + exception.getMessage()); exception.printStackTrace(); @@ -64,186 +53,97 @@ public class StructureManager { } private static void loadXmlStructureFile(File file) throws SAXException, IOException, InvalidXmlException { - Document base = WarpDriveConfig.getXmlDocumentBuilder().parse(file); + WarpDrive.logger.info("Loading structure data file " + file.getName()); + Document document = WarpDriveConfig.getXmlDocumentBuilder().parse(file); - ModCheckResults res = XmlPreprocessor.checkModRequirements(base.getDocumentElement()); - - if (!res.isEmpty()) { - WarpDrive.logger.info("Skippping structure " + file.getName() + " due to " + res); + // pre-process the file + String result = XmlPreprocessor.checkModRequirements(document.getDocumentElement()); + if (!result.isEmpty()) { + WarpDrive.logger.info("Skipping structure " + file.getName() + " due to " + result); return; } - XmlPreprocessor.doModReqSanitation(base); - XmlPreprocessor.doLogicPreprocessing(base); + XmlPreprocessor.doModReqSanitation(document); + XmlPreprocessor.doLogicPreprocessing(document); - NodeList structures = base.getElementsByTagName("structure"); - for (int i = 0; i < structures.getLength(); i++) { + // only add FillerSets + NodeList nodeListStructures = document.getElementsByTagName("structure"); + for (int structureIndex = 0; structureIndex < nodeListStructures.getLength(); structureIndex++) { - Element struct = (Element) structures.item(i); + Element elementStructure = (Element) nodeListStructures.item(structureIndex); - String group = struct.getAttribute("group"); - String name = struct.getAttribute("name"); + String group = elementStructure.getAttribute("group"); + if (group.isEmpty()) { + throw new InvalidXmlException("Structure " + (structureIndex + 1) + "/" + nodeListStructures.getLength() + " is missing a group attribute!"); + } - WarpDrive.logger.info("Loading structure " + name); + String name = elementStructure.getAttribute("name"); + if (name.isEmpty()) { + throw new InvalidXmlException("Structure " + (structureIndex + 1) + "/" + nodeListStructures.getLength() + " is missing a name attribute!"); + } - if (group.isEmpty()) - throw new InvalidXmlException("Structure must have a group!"); + WarpDrive.logger.info("- found structure " + group + ":" + name); - int radius = 0; - - if (group.equalsIgnoreCase("star")) { - stars.loadFromXML(new Star(radius), struct); - } else if (group.equalsIgnoreCase("moon")) { - moons.loadFromXML(new Planetoid(radius), struct); - } else if (group.equalsIgnoreCase("asteroid")) { - asteroids.loadFromXML(new Asteroid(), struct); + switch (group) { + case GROUP_STARS: + stars.loadFromXML(new Star(name), elementStructure); + break; + case GROUP_MOONS: + moons.loadFromXML(new Planetoid(name), elementStructure); + break; + case GROUP_ASTEROIDS: + asteroids.loadFromXML(new Asteroid(name), elementStructure); + break; + case GROUP_GASCLOUDS: + gasClouds.loadFromXML(new Asteroid(name), elementStructure); + break; + default: + throw new InvalidXmlException("Structure " + (structureIndex + 1) + "/" + nodeListStructures.getLength() + " has invalid group " + group); + // break; } } } - public static DeployableStructure getStructure(Random random, final String name, final String type) { + public static DeployableStructure getStructure(Random random, final String group, final String name) { if (name == null || name.length() == 0) { - if (type == null || type.length() == 0) { - return stars.next(random); - } else if (type.equalsIgnoreCase("star")) { - return stars.next(random); - } else if (type.equalsIgnoreCase("moon")) { - return moons.next(random); - } else if (type.equalsIgnoreCase("asteroid")) { - return asteroids.next(random); + if (group == null || group.isEmpty()) { + return null; + } else if (group.equalsIgnoreCase(GROUP_STARS)) { + return stars.getRandomEntry(random); + } else if (group.equalsIgnoreCase(GROUP_MOONS)) { + return moons.getRandomEntry(random); + } else if (group.equalsIgnoreCase(GROUP_ASTEROIDS)) { + return asteroids.getRandomEntry(random); } } else { - for (Star star : stars.elements()) { - if (star.getName().equals(name)) - return star; + if (group == null || group.isEmpty()) { + return null; + } else if (group.equalsIgnoreCase(GROUP_STARS)) { + return stars.getNamedEntry(name); + } else if (group.equalsIgnoreCase(GROUP_MOONS)) { + return moons.getNamedEntry(name); + } else if (group.equalsIgnoreCase(GROUP_ASTEROIDS)) { + return asteroids.getNamedEntry(name); } } - // not found or nothing defined => return null + // not found or nothing defined return null; } public static DeployableStructure getStar(Random random, final String name) { - return getStructure(random, name, "star"); + return getStructure(random, GROUP_STARS, name); } public static DeployableStructure getMoon(Random random, final String name) { - return getStructure(random, name, "moon"); + return getStructure(random, GROUP_MOONS, name); } public static DeployableStructure getAsteroid(Random random, final String name) { - return getStructure(random, name, "asteroid"); + return getStructure(random, GROUP_ASTEROIDS, name); } public static DeployableStructure getGasCloud(Random random, final String name) { - return getStructure(random, name, "cloud"); - } - - /** - * Collection of elements with weights. Helps to select element with controlled odds. - * - * @author ncrashed - * - * @param - */ - private static class RandomCollection { - private final NavigableMap weightMap = new TreeMap(); - private double totalWeight = 0; - private final NavigableMap ratioMap = new TreeMap(); - private double totalRatio = 0; - private final ArrayList list = new ArrayList(); - - - /** - * Add new object and its weight. - * @param weight Used for random pick. The higher the value is relatively to others, the higher odds of choosing the object. - * @param obj Object to add - */ - public void add(double weight, E obj) { - if (weight <= 0) { - WarpDrive.logger.warn("Structure weight is negative or zero, skipping"); - return; - } - totalWeight += weight; - weightMap.put(totalWeight, obj); - list.add(obj); - } - - /** - * Add new object and its ratio. - * Warning: if total ratio goes higher than 1.0, element won't be added to collection. - * @param ratio Chance of random pick in range (0, 1.0]. In contrast to weights, ratio is fixed and chances don't change if you add more elements. - * @param obj Object to add - */ - public void addRatio(double ratio, E obj) { - if (ratio <= 0 || ratio >= 1.0) { - WarpDrive.logger.warn("Structure ratio isn't in (0, 1.0] bounds, skipping"); - return; - } - - if (totalRatio + ratio > 1.0) { - WarpDrive.logger.warn("Structures total ratio is greater than 1.0, skipping"); - return; - } - totalRatio += ratio; - ratioMap.put(totalRatio, obj); - list.add(obj); - } - - /** - * Pick random object according their weights - * @param random - * @return Random object or null if there is no objects to pick. - */ - public E next(Random random) { - double value = random.nextDouble(); - - if (value < totalRatio) { // hit ratio part of values - return ratioMap.ceilingEntry(value).getValue(); - } else { // hit dynamic part of values, weighted ones - double weight = (value - totalRatio)*totalWeight; - return weightMap.ceilingEntry(weight).getValue(); - } - } - - /** - * @return All registered objects - */ - public ArrayList elements() { - return list; - } - - /** - * Loads object from given XML element and parses configurations for weighted pick. - * @param obj Object to load from *struct* - * @param struct Part of XML config - * @throws InvalidXmlException - */ - public void loadFromXML(E obj, Element struct) throws InvalidXmlException { - obj.loadFromXmlElement(struct); - - try { - String ratioStr = struct.getAttribute("ratio"); - if(!ratioStr.isEmpty()) { - double ratio = Double.parseDouble(ratioStr); - this.addRatio(ratio, obj); - } else { // try weight - try { - int weight = 1; - String weightStr = struct.getAttribute("weight"); - if(!weightStr.isEmpty()) { - weight = Integer.parseInt(weightStr); - weight = Math.max(1, weight); - } - - this.add(weight, obj); - } catch (NumberFormatException gdbg) { - throw new InvalidXmlException("Weight must be int!"); - } - } - } catch (NumberFormatException gdbg) { - throw new InvalidXmlException("Ratio must be double!"); - } - } + return getStructure(random, GROUP_GASCLOUDS, name); } } diff --git a/src/main/java/cr0s/warpdrive/world/EntitySphereGen.java b/src/main/java/cr0s/warpdrive/world/EntitySphereGen.java index e779fd71..060523c6 100644 --- a/src/main/java/cr0s/warpdrive/world/EntitySphereGen.java +++ b/src/main/java/cr0s/warpdrive/world/EntitySphereGen.java @@ -65,13 +65,14 @@ public final class EntitySphereGen extends Entity { private ArrayList blocks; private Orb orb; + private int[] thicknesses; private boolean replace; public EntitySphereGen(World world) { super(world); } - public EntitySphereGen(World world, int x, int y, int z, int radius, Orb orb, boolean replace) { + public EntitySphereGen(World world, int x, int y, int z, Orb orb, int[] thicknesses, int radius, boolean replace) { super(world); this.xCoord = x; this.posX = x; @@ -86,6 +87,7 @@ public final class EntitySphereGen extends Entity { blocks = new ArrayList(this.pregenSize); this.ticksDelay = world.rand.nextInt(60); this.orb = orb; + this.thicknesses = thicknesses; this.replace = replace; } @@ -97,10 +99,10 @@ public final class EntitySphereGen extends Entity { @Override public void onUpdate() { - if (FMLCommonHandler.instance().getEffectiveSide().isClient()) { + if (worldObj.isRemote) { return; } - + if (ticksDelay > 0) { ticksDelay--; return; @@ -171,10 +173,10 @@ public final class EntitySphereGen extends Entity { if (dSq > radius) continue; - int rad = (int) Math.ceil(dSq); + int range = (int) Math.ceil(dSq); // Add blocks to memory - OrbShell orbShell = orb.getShellForRadius(rad); + OrbShell orbShell = orb.getShellForRadius(thicknesses, range); MetaBlock metablock = orbShell.getRandomBlock(rand); addBlock(new JumpBlock(metablock.block, metablock.metadata, xCoord + x, yCoord + y, zCoord + z)); @@ -208,8 +210,9 @@ public final class EntitySphereGen extends Entity { } private void addBlock(JumpBlock jb) { - if (blocks == null) + if (blocks == null) { return; + } // Replace water with random gas (ship in moon) if (worldObj.getBlock(jb.x, jb.y, jb.z).isAssociatedBlock(Blocks.water)) { if (worldObj.rand.nextInt(50) != 1) { @@ -220,8 +223,9 @@ public final class EntitySphereGen extends Entity { return; } // Do not replace existing blocks if fillingSphere is true - if (!replace && !worldObj.isAirBlock(jb.x, jb.y, jb.z)) + if (!replace && !worldObj.isAirBlock(jb.x, jb.y, jb.z)) { return; + } blocks.add(jb); } diff --git a/src/main/java/cr0s/warpdrive/world/SpaceWorldGenerator.java b/src/main/java/cr0s/warpdrive/world/SpaceWorldGenerator.java index 09beb18a..6f0f5ee7 100644 --- a/src/main/java/cr0s/warpdrive/world/SpaceWorldGenerator.java +++ b/src/main/java/cr0s/warpdrive/world/SpaceWorldGenerator.java @@ -269,7 +269,6 @@ public class SpaceWorldGenerator implements IWorldGenerator { public static void generateRandomAsteroid(World world, int x, int y, int z) { DeployableStructure asteroid = StructureManager.getAsteroid(world.rand, null); - WarpDrive.logger.info("Generating asteroid (class " + asteroid + ") at " + x + " " + y + " " + z); asteroid.generate(world, world.rand, x, y, z); @@ -296,34 +295,37 @@ public class SpaceWorldGenerator implements IWorldGenerator { } */ } - + public static void generateSphereDirect( - World world, int xCoord, int yCoord, int zCoord, Orb orb, Random rand) { - double radiusC = orb.getHeight() / 2 + 0.5D; // Radius from center of block + final int[] thicknesses, Orb orb, World world, int xCoord, int yCoord, int zCoord) { + int totalThickness = 0; + for (int thickness : thicknesses) { + totalThickness += thickness; + } + double radiusC = totalThickness + 0.5D; // Radius from center of block double radiusSq = radiusC * radiusC; // Optimization to avoid sqrts... // sphere int ceilRadius = (int) Math.ceil(radiusC); - + // Pass the cube and check points for sphere equation x^2 + y^2 + z^2 = r^2 for (int x = 0; x <= ceilRadius; x++) { - double x2 = (x + 0.5D) * (x + 0.5D); + double dX2 = (x + 0.5D) * (x + 0.5D); for (int y = 0; y <= ceilRadius; y++) { - double y2 = (y + 0.5D) * (y + 0.5D); + double dX2Y2 = dX2 + (y + 0.5D) * (y + 0.5D); for (int z = 0; z <= ceilRadius; z++) { - double z2 = (z + 0.5D) * (z + 0.5D); - int dSq = (int) Math.sqrt(x2 + y2 + z2); // Distance from current position - //TODO: Find quicker form of sqrt - + double dZ2 = (z + 0.5D) * (z + 0.5D); + double dSq = dX2Y2 + dZ2; // squared distance from current position + // Skip too far blocks if (dSq > radiusSq) { continue; } - + // Place blocks // cheat by using axial symmetry so we don't create random numbers too frequently - - OrbShell orbShell = orb.getShellForRadius(dSq); - MetaBlock metablock = orbShell.getRandomBlock(rand); + + OrbShell orbShell = orb.getShellForRadius(thicknesses, (int)Math.sqrt(dSq)); + MetaBlock metablock = orbShell.getRandomBlock(world.rand); world.setBlock(xCoord + x, yCoord + y, zCoord + z, metablock.block, metablock.metadata, 2); world.setBlock(xCoord - x, yCoord + y, zCoord + z, metablock.block, metablock.metadata, 2); world.setBlock(xCoord + x, yCoord - y, zCoord + z, metablock.block, metablock.metadata, 2); diff --git a/src/main/resources/config/filler-default.xml b/src/main/resources/config/filler-default.xml index 88cce4a3..d2e79b8d 100644 --- a/src/main/resources/config/filler-default.xml +++ b/src/main/resources/config/filler-default.xml @@ -3,7 +3,7 @@ - + @@ -32,20 +32,20 @@ - + - + - + @@ -53,19 +53,19 @@ - + - + - + diff --git a/src/main/resources/config/filler-netherores.xml b/src/main/resources/config/filler-netherores.xml index 4c713b29..23dbed7a 100644 --- a/src/main/resources/config/filler-netherores.xml +++ b/src/main/resources/config/filler-netherores.xml @@ -3,7 +3,7 @@ - + diff --git a/src/main/resources/config/filler-undergroundbiomes.xml b/src/main/resources/config/filler-undergroundbiomes.xml index a76576df..07f71e6e 100644 --- a/src/main/resources/config/filler-undergroundbiomes.xml +++ b/src/main/resources/config/filler-undergroundbiomes.xml @@ -5,7 +5,7 @@ - + @@ -15,11 +15,11 @@ - + - + diff --git a/src/main/resources/config/structures-default.xml b/src/main/resources/config/structures-default.xml index 50b46cc3..9323bb38 100644 --- a/src/main/resources/config/structures-default.xml +++ b/src/main/resources/config/structures-default.xml @@ -3,22 +3,87 @@ - - - + + + - - - + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -68,22 +133,24 @@ - - + + + + - - + + - - + +