Make loading of custom block definitions much more forgiving
parent
ee4f8aa0d5
commit
2d841d7c3c
|
@ -8,7 +8,6 @@ import com.google.common.collect.ImmutableMap;
|
|||
import com.google.common.collect.ImmutableSet;
|
||||
import org.pepsoft.util.CSVDataSource;
|
||||
import org.pepsoft.util.Pair;
|
||||
import org.pepsoft.worldpainter.Configuration;
|
||||
import org.pepsoft.worldpainter.Platform;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -31,6 +30,7 @@ import static org.pepsoft.minecraft.Constants.*;
|
|||
import static org.pepsoft.minecraft.HorizontalOrientationScheme.CARDINAL_DIRECTIONS;
|
||||
import static org.pepsoft.minecraft.HorizontalOrientationScheme.STAIR_CORNER;
|
||||
import static org.pepsoft.minecraft.Material.PropertyType.*;
|
||||
import static org.pepsoft.minecraft.MaterialImporter.importCustomMaterials;
|
||||
import static org.pepsoft.util.ObjectMapperHolder.OBJECT_MAPPER;
|
||||
import static org.pepsoft.worldpainter.Constants.UNKNOWN_MATERIAL_COLOUR;
|
||||
import static org.pepsoft.worldpainter.Platform.Capability.NAME_BASED;
|
||||
|
@ -1615,64 +1615,7 @@ public final class Material implements Serializable {
|
|||
throw new RuntimeException("I/O error while reading Minecraft materials database materials.csv from classpath", e);
|
||||
}
|
||||
|
||||
final File customMaterialsDir = new File(Configuration.getConfigDir(), "materials");
|
||||
if (customMaterialsDir.isDirectory()) {
|
||||
final File[] customSpecFiles = customMaterialsDir.listFiles(pathname -> pathname.isFile() && pathname.getName().toLowerCase().endsWith(".csv"));
|
||||
if (customSpecFiles != null) {
|
||||
for (File customSpecFile: customSpecFiles) {
|
||||
int count = 0;
|
||||
final Set<String> namespaces = new HashSet<>();
|
||||
try (Reader in = new InputStreamReader(new FileInputStream(customSpecFile), UTF_8)) {
|
||||
CSVDataSource csvDataSource = new CSVDataSource();
|
||||
csvDataSource.openForReading(in);
|
||||
do {
|
||||
Map<String, Object> materialSpecs = new HashMap<>();
|
||||
String name = csvDataSource.getString("name");
|
||||
materialSpecs.put("name", name);
|
||||
String str = csvDataSource.getString("discriminator");
|
||||
if (! isNullOrEmpty(str)) {
|
||||
materialSpecs.put("discriminator", ImmutableSet.copyOf(str.split(",")));
|
||||
}
|
||||
str = csvDataSource.getString("properties");
|
||||
if (! isNullOrEmpty(str)) {
|
||||
materialSpecs.put("properties", stream(str.split(",")).map(PropertyDescriptor::fromString).collect(toMap(d -> d.name, identity())));
|
||||
}
|
||||
materialSpecs.put("opacity", csvDataSource.getInt("opacity"));
|
||||
materialSpecs.put("receivesLight", csvDataSource.getBoolean("receivesLight"));
|
||||
materialSpecs.put("terrain", false);
|
||||
materialSpecs.put("insubstantial", csvDataSource.getBoolean("insubstantial"));
|
||||
materialSpecs.put("veryInsubstantial", csvDataSource.getBoolean("insubstantial")); // Copy "insubstantial"
|
||||
materialSpecs.put("resource", csvDataSource.getBoolean("resource"));
|
||||
materialSpecs.put("tileEntity", csvDataSource.getBoolean("tileEntity"));
|
||||
str = csvDataSource.getString("tileEntityId");
|
||||
if (! isNullOrEmpty(str)) {
|
||||
materialSpecs.put("tileEntityId", str);
|
||||
}
|
||||
materialSpecs.put("treeRelated", csvDataSource.getBoolean("treeRelated"));
|
||||
materialSpecs.put("vegetation", csvDataSource.getBoolean("vegetation"));
|
||||
materialSpecs.put("blockLight", csvDataSource.getInt("blockLight"));
|
||||
materialSpecs.put("natural", csvDataSource.getBoolean("natural"));
|
||||
materialSpecs.put("watery", csvDataSource.getBoolean("watery"));
|
||||
str = csvDataSource.getString("colour");
|
||||
if (! isNullOrEmpty(str)) {
|
||||
materialSpecs.put("colour", Integer.parseUnsignedInt(str, 16));
|
||||
}
|
||||
|
||||
MATERIAL_SPECS.computeIfAbsent(name, s -> new HashSet<>()).add(materialSpecs);
|
||||
final int p = name.indexOf(':');
|
||||
final String namespace = name.substring(0, p);
|
||||
namespaces.add(namespace);
|
||||
SIMPLE_NAMES_BY_NAMESPACE.computeIfAbsent(namespace, s -> new HashSet<>()).add(name.substring(p + 1));
|
||||
csvDataSource.next();
|
||||
count++;
|
||||
} while (! csvDataSource.isEndOfFile());
|
||||
} catch (RuntimeException | IOException e) {
|
||||
throw new RuntimeException("I/O error while reading Minecraft materials database materials.csv from classpath", e);
|
||||
}
|
||||
logger.info("Loaded {} custom block(s) with namespace(s) {} from {}", count, namespaces, customSpecFile.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
importCustomMaterials(MATERIAL_SPECS, SIMPLE_NAMES_BY_NAMESPACE);
|
||||
}
|
||||
|
||||
private static final Set<String> SNOW_ON = ImmutableSet.of(MC_SNOW_BLOCK, MC_POWDER_SNOW);
|
||||
|
|
|
@ -0,0 +1,149 @@
|
|||
package org.pepsoft.minecraft;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import org.pepsoft.util.CSVDataSource;
|
||||
import org.pepsoft.worldpainter.Configuration;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static java.util.Arrays.stream;
|
||||
import static java.util.function.Function.identity;
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
import static org.pepsoft.minecraft.Constants.MC_DIRT_PATH;
|
||||
import static org.pepsoft.minecraft.Constants.MC_GRASS_PATH;
|
||||
|
||||
public class MaterialImporter {
|
||||
static void importCustomMaterials(Map<String, Set<Map<String, Object>>> materialSpecs, Map<String, Set<String>> simpleNamesByNamespace) {
|
||||
final File customMaterialsDir = new File(Configuration.getConfigDir(), "materials");
|
||||
if (customMaterialsDir.isDirectory()) {
|
||||
final File[] customSpecFiles = customMaterialsDir.listFiles(pathname -> pathname.isFile() && pathname.getName().toLowerCase().endsWith(".csv"));
|
||||
if (customSpecFiles != null) {
|
||||
for (File customSpecFile: customSpecFiles) {
|
||||
int count = 0;
|
||||
final Set<String> namespaces = new HashSet<>();
|
||||
try (Reader in = new InputStreamReader(new FileInputStream(customSpecFile), UTF_8)) {
|
||||
CSVDataSource csvDataSource = new CSVDataSource();
|
||||
csvDataSource.openForReading(in);
|
||||
do {
|
||||
Map<String, Object> myMaterialSpecs = new HashMap<>();
|
||||
String name = csvDataSource.getString("name");
|
||||
myMaterialSpecs.put("name", name);
|
||||
String str = csvDataSource.getString("discriminator", null);
|
||||
if (! isNullOrEmpty(str)) {
|
||||
myMaterialSpecs.put("discriminator", ImmutableSet.copyOf(str.split(",")));
|
||||
}
|
||||
str = csvDataSource.getString("properties", null);
|
||||
if (! isNullOrEmpty(str)) {
|
||||
myMaterialSpecs.put("properties", stream(str.split(",")).map(Material.PropertyDescriptor::fromString).collect(toMap(d -> d.name, identity())));
|
||||
}
|
||||
myMaterialSpecs.put("opacity", csvDataSource.getInt("opacity", guessOpacity(name)));
|
||||
myMaterialSpecs.put("receivesLight", csvDataSource.getBoolean("receivesLight", guessReceivesLight(name)));
|
||||
myMaterialSpecs.put("terrain", false);
|
||||
final boolean insubstantial = csvDataSource.getBoolean("insubstantial", guessInsubstantial(name));
|
||||
myMaterialSpecs.put("insubstantial", insubstantial);
|
||||
myMaterialSpecs.put("veryInsubstantial", csvDataSource.getBoolean("insubstantial", insubstantial || guessVeryInsubstantial(name)));
|
||||
myMaterialSpecs.put("resource", csvDataSource.getBoolean("resource", guessResource(name)));
|
||||
final boolean tileEntity = csvDataSource.getBoolean("tileEntity", false);
|
||||
myMaterialSpecs.put("tileEntity", tileEntity);
|
||||
if (tileEntity) {
|
||||
myMaterialSpecs.put("tileEntityId", csvDataSource.getString("tileEntityId"));
|
||||
}
|
||||
final boolean treeRelated = csvDataSource.getBoolean("treeRelated", guessTreeRelated(name));
|
||||
myMaterialSpecs.put("treeRelated", treeRelated);
|
||||
final boolean vegetation = csvDataSource.getBoolean("vegetation", (! treeRelated) && guessVegetation(name));
|
||||
myMaterialSpecs.put("vegetation", vegetation);
|
||||
myMaterialSpecs.put("blockLight", csvDataSource.getInt("blockLight", 0));
|
||||
myMaterialSpecs.put("natural", csvDataSource.getBoolean("natural", vegetation || treeRelated || guessNatural(name)));
|
||||
myMaterialSpecs.put("watery", csvDataSource.getBoolean("watery", false));
|
||||
str = csvDataSource.getString("colour", null);
|
||||
if (! isNullOrEmpty(str)) {
|
||||
try {
|
||||
myMaterialSpecs.put("colour", Integer.parseUnsignedInt(str, 16));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException("Not a valid hexadecimal integer value for column colour: \"" + str + "\"", e);
|
||||
}
|
||||
}
|
||||
|
||||
materialSpecs.computeIfAbsent(name, s -> new HashSet<>()).add(myMaterialSpecs);
|
||||
final int p = name.indexOf(':');
|
||||
final String namespace = name.substring(0, p);
|
||||
namespaces.add(namespace);
|
||||
simpleNamesByNamespace.computeIfAbsent(namespace, s -> new HashSet<>()).add(name.substring(p + 1));
|
||||
csvDataSource.next();
|
||||
count++;
|
||||
} while (! csvDataSource.isEndOfFile());
|
||||
logger.info("Loaded {} custom block(s) with namespace(s) {} from {}", count, namespaces, customSpecFile.getName());
|
||||
} catch (RuntimeException | IOException e) {
|
||||
final String message = String.format("%s while reading custom block definition(s) from %s\nMessage: %s", e.getClass().getSimpleName(), customSpecFile.getName(), e.getMessage());
|
||||
logger.error(message, e);
|
||||
errors.add(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int guessOpacity(String name) {
|
||||
if (name.endsWith("_slab") || name.endsWith("_stairs") || name.contains("block") || name.endsWith("_log") || name.endsWith("_wood") || name.endsWith("_stem") || name.endsWith("_hyphea") || name.contains("bricks")) {
|
||||
return 15;
|
||||
} else if (name.contains("leaves")) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean guessResource(String name) {
|
||||
return name.contains("ore");
|
||||
}
|
||||
|
||||
public static boolean guessTreeRelated(String name) {
|
||||
return name.endsWith("_log") || name.endsWith("_wood") || name.endsWith("_stem") || name.endsWith("_hyphea") || name.endsWith("_leaves") || name.endsWith("_sapling");
|
||||
}
|
||||
|
||||
public static boolean guessVeryInsubstantial(String name) {
|
||||
return guessVegetation(name) || name.contains("leaves");
|
||||
}
|
||||
|
||||
public static boolean guessInsubstantial(String name) {
|
||||
return guessVegetation(name);
|
||||
}
|
||||
|
||||
public static boolean guessVegetation(String name) {
|
||||
return (! name.endsWith("_block"))
|
||||
&& (! guessTreeRelated(name))
|
||||
&& (name.contains("leaf")
|
||||
|| name.contains("vine")
|
||||
|| name.contains("fungus")
|
||||
|| name.contains("roots")
|
||||
|| name.contains("azalea")
|
||||
|| name.contains("flowering")
|
||||
|| name.contains("lichen")
|
||||
|| name.contains("moss")
|
||||
|| name.contains("stem")
|
||||
|| name.contains("blossom"));
|
||||
}
|
||||
|
||||
public static boolean guessNatural(String material) {
|
||||
return (guessVegetation(material) || guessTreeRelated(material))
|
||||
&& (! material.contains("stripped"));
|
||||
}
|
||||
/**
|
||||
* Guess whether a material receives light unto itself, despite being opaque to surrounding blocks.
|
||||
*/
|
||||
public static boolean guessReceivesLight(String name) {
|
||||
return NON_TRANSMITTING_TRANSPARENT_BLOCKS.contains(name)
|
||||
|| name.endsWith("_slab")
|
||||
|| name.endsWith("_stairs");
|
||||
}
|
||||
|
||||
public static final List<String> errors = new ArrayList<>();
|
||||
|
||||
private static final Set<String> NON_TRANSMITTING_TRANSPARENT_BLOCKS = ImmutableSet.of(MC_DIRT_PATH, MC_GRASS_PATH);
|
||||
private static final Logger logger = LoggerFactory.getLogger(MaterialImporter.class);
|
||||
}
|
|
@ -12,6 +12,7 @@ import ch.qos.logback.core.util.StatusPrinter;
|
|||
import com.jidesoft.plaf.LookAndFeelFactory;
|
||||
import com.jidesoft.utils.Lm;
|
||||
import org.intellij.lang.annotations.Language;
|
||||
import org.pepsoft.minecraft.MaterialImporter;
|
||||
import org.pepsoft.util.DesktopUtils;
|
||||
import org.pepsoft.util.FileUtils;
|
||||
import org.pepsoft.util.GUIUtils;
|
||||
|
@ -50,6 +51,7 @@ import java.util.*;
|
|||
import java.util.prefs.BackingStoreException;
|
||||
import java.util.prefs.Preferences;
|
||||
|
||||
import static javax.swing.JOptionPane.ERROR_MESSAGE;
|
||||
import static org.pepsoft.util.GUIUtils.getUIScale;
|
||||
import static org.pepsoft.worldpainter.Constants.ATTRIBUTE_KEY_PLUGINS;
|
||||
import static org.pepsoft.worldpainter.Constants.ATTRIBUTE_KEY_SAFE_MODE;
|
||||
|
@ -511,6 +513,10 @@ public class Main {
|
|||
if (myConfig.isAutosaveEnabled() && autosaveInhibited) {
|
||||
JOptionPane.showMessageDialog(app, "Another instance of WorldPainter is already running.\nAutosave will therefore be disabled in this instance of WorldPainter!", "Autosave Disabled", JOptionPane.WARNING_MESSAGE);
|
||||
}
|
||||
for (String error: MaterialImporter.errors) {
|
||||
DesktopUtils.beep();
|
||||
JOptionPane.showMessageDialog(app, error, "Custom Object Definition Error", ERROR_MESSAGE);
|
||||
}
|
||||
if (! DonationDialog.maybeShowDonationDialog(app)) {
|
||||
MerchDialog.maybeShowMerchDialog(app);
|
||||
}
|
||||
|
@ -532,7 +538,7 @@ public class Main {
|
|||
|
||||
// Report the error
|
||||
logger.error("Exception while initialising configuration", e);
|
||||
JOptionPane.showMessageDialog(null, "Could not read configuration file! Resetting configuration.\n\nException type: " + e.getClass().getSimpleName() + "\nMessage: " + e.getMessage(), "Configuration Error", JOptionPane.ERROR_MESSAGE);
|
||||
JOptionPane.showMessageDialog(null, "Could not read configuration file! Resetting configuration.\n\nException type: " + e.getClass().getSimpleName() + "\nMessage: " + e.getMessage(), "Configuration Error", ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
@Language("HTML")
|
||||
|
|
|
@ -9,6 +9,7 @@ import java.util.HashMap;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
|
||||
|
@ -96,9 +97,21 @@ public class CSVDataSource {
|
|||
* @return The value of the specified column in the current row.
|
||||
*/
|
||||
public String getString(String columnName) {
|
||||
checkColumnName(columnName);
|
||||
return getString(columnsByName.get(columnName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a string-typed value by column name.
|
||||
*
|
||||
* @param columnName The name of the column.
|
||||
* @param defaultValue The value to return if the specified column is not present, or the value is not set.
|
||||
* @return The value of the specified column in the current row.
|
||||
*/
|
||||
public String getString(String columnName, String defaultValue) {
|
||||
return columnsByName.containsKey(columnName) ? getString(columnsByName.get(columnName), defaultValue) : defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a string-typed value by column index.
|
||||
*
|
||||
|
@ -109,6 +122,18 @@ public class CSVDataSource {
|
|||
return currentRow.get(columnIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a string-typed value by column index.
|
||||
*
|
||||
* @param columnIndex The index of the column.
|
||||
* @return The value of the specified column in the current row, or {@code defaultValue} if the column does not
|
||||
* exist or the value is not set.
|
||||
*/
|
||||
public String getString(int columnIndex, String defaultValue) {
|
||||
final String value = currentRow.get(columnIndex);
|
||||
return isNullOrEmpty(value) ? defaultValue : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a string-typed value by column name. {@code null} values are
|
||||
* supported but are converted into empty strings.
|
||||
|
@ -117,6 +142,7 @@ public class CSVDataSource {
|
|||
* @param value The value to store in the column.
|
||||
*/
|
||||
public void setString(String columnName, String value) {
|
||||
checkColumnName(columnName);
|
||||
setString(columnsByName.get(columnName), value);
|
||||
}
|
||||
|
||||
|
@ -132,21 +158,57 @@ public class CSVDataSource {
|
|||
}
|
||||
|
||||
public int getInt(String columnName) {
|
||||
return Integer.parseInt(getString(columnName));
|
||||
checkColumnName(columnName);
|
||||
try {
|
||||
return Integer.parseInt(getString(columnName));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException("Not a valid integer value for column " + columnName + ": \"" + getString(columnName) + "\"", e);
|
||||
}
|
||||
}
|
||||
|
||||
public int getInt(String columnName, int defaultValue) {
|
||||
if (! columnsByName.containsKey(columnName)) {
|
||||
return defaultValue;
|
||||
} else {
|
||||
final String stringValue = getString(columnName);
|
||||
try {
|
||||
return (stringValue != null) ? Integer.parseInt(stringValue) : defaultValue;
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException("Not a valid integer value for column " + columnName + ": \"" + getString(columnName) + "\"", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setInt(String columnName, int value) {
|
||||
checkColumnName(columnName);
|
||||
setString(columnName, Integer.toString(value));
|
||||
}
|
||||
|
||||
public boolean getBoolean(String columnName) {
|
||||
checkColumnName(columnName);
|
||||
return Boolean.parseBoolean(getString(columnName));
|
||||
}
|
||||
|
||||
public boolean getBoolean(String columnName, boolean defaultValue) {
|
||||
if (! columnsByName.containsKey(columnName)) {
|
||||
return defaultValue;
|
||||
} else {
|
||||
final String stringValue = getString(columnName);
|
||||
return (stringValue != null) ? Boolean.parseBoolean(stringValue) : defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
public void setBoolean(String columnName, boolean value) {
|
||||
checkColumnName(columnName);
|
||||
setString(columnName, Boolean.toString(value));
|
||||
}
|
||||
|
||||
private void checkColumnName(String columnName) {
|
||||
if (! columnsByName.containsKey(columnName)) {
|
||||
throw new IllegalArgumentException("There is no column named \"" + columnName + "\"");
|
||||
}
|
||||
}
|
||||
|
||||
private void readHeaders() throws IOException {
|
||||
List<String> headers = readLine();
|
||||
columnsByName = new HashMap<>();
|
||||
|
|
Loading…
Reference in New Issue