Skip to content

Commit

Permalink
Add support for specifying entities
Browse files Browse the repository at this point in the history
  • Loading branch information
TheEpicBlock committed Jul 29, 2022
1 parent 868dfff commit c64a00f
Show file tree
Hide file tree
Showing 17 changed files with 396 additions and 117 deletions.
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,18 @@ block "test:my_slab" {
}
}

// You can also configure items, if you need to
// You can also configure entities
entity "test:my_test_entity" {
base "minecraft:zombie"
name "yeet"
}

entity "test:my_test_entity_2" {
base "minecraft:zombie"
name null
}

// And you can also configure items, if you need to
item "test:magic_sword" {
replacement "minecraft:diamond_sword"
enchanted true
Expand Down
4 changes: 2 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ org.gradle.jvmargs=-Xmx1G
loader_version=0.14.6

# Mod Properties
mod_version = 0.2.0
mod_version = 0.3.0
maven_group = nl.theepicblock
archives_base_name = polyconfig

# Dependencies
fabric_version=0.55.1+1.19
polymc_version=5.1.1+1.19
polymc_version=5.2.0+1.19
18 changes: 0 additions & 18 deletions src/main/java/nl/theepicblock/polyconfig/KDLUtil.java

This file was deleted.

44 changes: 24 additions & 20 deletions src/main/java/nl/theepicblock/polyconfig/PolyConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,37 +6,44 @@
import io.github.theepicblock.polymc.api.PolyMcEntrypoint;
import io.github.theepicblock.polymc.api.PolyRegistry;
import io.github.theepicblock.polymc.api.block.BlockStateManager;
import io.github.theepicblock.polymc.impl.poly.block.FunctionBlockStatePoly;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.util.Identifier;
import nl.theepicblock.polyconfig.block.BlockNodeParser;
import nl.theepicblock.polyconfig.block.ConfigFormatException;
import nl.theepicblock.polyconfig.block.CustomBlockPoly;
import nl.theepicblock.polyconfig.entity.CustomEntityWizard;
import nl.theepicblock.polyconfig.entity.EntityNodeParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.FileVisitOption;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

public class PolyConfig implements PolyMcEntrypoint {
private static final int CURRENT_VERSION = 1;
public static final Logger LOGGER = LoggerFactory.getLogger("polyconfig");

/**
* A temporary record that holds the parsed nodes before they're applied
*/
public record Declarations(Map<Identifier, BlockNodeParser.BlockEntry> blockDeclarations, Map<Identifier, EntityNodeParser.EntityEntry> entityDeclarations) {}

@Override
public void registerPolys(PolyRegistry registry) {
var parser = new KDLParser();
var blockDeclarations = new HashMap<Identifier, BlockNodeParser.BlockEntry>();
var declarations = new Declarations(new HashMap<>(), new HashMap<>());
var configdir = FabricLoader.getInstance().getConfigDir();

var oldLocation = configdir.resolve("polyconfig.kdl").toFile();
var polyconfigdir = configdir.resolve("polyconfig");
if (oldLocation.exists()) {
// Still uses the old location, respect that
handleFile(oldLocation, parser, blockDeclarations);
handleFile(oldLocation, parser, declarations);
} else {
// Try see if there's a file at the new location, if not we should create a directory
if (!polyconfigdir.toFile().exists()) {
Expand All @@ -52,25 +59,30 @@ public void registerPolys(PolyRegistry registry) {
try {
if (Files.exists(polyconfigdir)) {
Files.walk(polyconfigdir, Integer.MAX_VALUE, FileVisitOption.FOLLOW_LINKS).forEachOrdered(path -> {
handleFile(path.toFile(), parser, blockDeclarations);
handleFile(path.toFile(), parser, declarations);
});
}
} catch (IOException e) {
e.printStackTrace();
}

// Apply block nodes to PolyMc
blockDeclarations.forEach((identifier, blockEntry) -> {
declarations.blockDeclarations().forEach((identifier, blockEntry) -> {
registry.registerBlockPoly(
blockEntry.moddedBlock(),
new FunctionBlockStatePoly(
new CustomBlockPoly(
blockEntry.moddedBlock(),
(state, isUniqueCallback) -> blockEntry.rootNode().grabBlockState(state, isUniqueCallback, registry.getSharedValues(BlockStateManager.KEY)),
blockEntry.merger()));
});

// Apply entity nodes
declarations.entityDeclarations().forEach((identifier, entityEntry) -> {
registry.registerEntityPoly(entityEntry.base(), (info, entity) -> new CustomEntityWizard<>(info, entity, entityEntry.base(), entityEntry.name()));
});
}

private static void handleFile(File configFile, KDLParser parser, Map<Identifier, BlockNodeParser.BlockEntry> blockDeclarations) {
private static void handleFile(File configFile, KDLParser parser, Declarations declarations) {
var path = FabricLoader.getInstance().getConfigDir().relativize(configFile.toPath());
try (var stream = new FileInputStream(configFile)) {
var config = parser.parse(stream);
Expand All @@ -89,10 +101,10 @@ private static void handleFile(File configFile, KDLParser parser, Map<Identifier
// These errors are only warnings, so we wrap this in another try block
try {
switch (node.getIdentifier()) {
case "version" -> {
}
case "block" -> BlockNodeParser.parseBlockNode(node, blockDeclarations);
case "version" -> {}
case "block" -> BlockNodeParser.parseBlockNode(node, declarations.blockDeclarations);
case "item" -> handleItemNode(node);
case "entity" -> EntityNodeParser.parseEntityNode(node, declarations.entityDeclarations);
default -> throw unknownNode(node);
}
} catch (ConfigFormatException e) {
Expand All @@ -115,14 +127,6 @@ private static void handleItemNode(KDLNode node) {

}

private static InputStream readConfigFile() throws IOException {
var path = FabricLoader.getInstance().getConfigDir().resolve("polyconfig.kdl");
if (!Files.exists(path)) {

}
return new FileInputStream(path.toFile());
}

private static ConfigFormatException multipleVersionDeclarations(int found) {
return new ConfigFormatException("Expected 1 version declaration. Found "+found)
.withHelp("Your config file includes `version ...` multiple times. Try deleting all but the topmost one.");
Expand Down
100 changes: 100 additions & 0 deletions src/main/java/nl/theepicblock/polyconfig/Utils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package nl.theepicblock.polyconfig;

import dev.hbeck.kdl.objects.KDLNode;
import dev.hbeck.kdl.objects.KDLString;
import dev.hbeck.kdl.objects.KDLValue;
import net.minecraft.util.Identifier;
import net.minecraft.util.registry.Registry;
import nl.theepicblock.polyconfig.block.ConfigFormatException;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;

public class Utils {
public static List<KDLNode> getChildren(KDLNode node) {
var childDoc = node.getChild();
if (childDoc.isPresent()) {
return childDoc.get().getNodes();
} else {
return Collections.emptyList();
}
}

public static <T> void getFromRegistry(KDLString string, String v, Registry<T> registry, GetFromRegistryConsumer<T> consumer) throws ConfigFormatException {
boolean isRegex;
if (string.getType().isEmpty()) {
// Automatically determine if it's a regex
isRegex = Identifier.tryParse(string.getValue()) == null;
} else {
isRegex = switch (string.getType().get()) {
case "identifier" -> false;
case "id" -> false;
case "regex" -> true;
default -> throw new ConfigFormatException("Invalid type "+string.getType().get());
};
}
if (isRegex) {
var predicate = Pattern.compile(string.getValue()).asMatchPredicate();
var count = 0;
var exceptions = new ArrayList<ConfigFormatException>();
for (var id : registry.getIds()) {
if (predicate.test(id.toString())) {
count++;
try {
consumer.accept(id, registry.get(id), true);
} catch (ConfigFormatException e) {
exceptions.add(e);
}
}
}

if (!exceptions.isEmpty()) {
if (exceptions.size() == 1) {
throw exceptions.get(0);
} else {
throw new ConfigFormatException("Warning: regex caused multiple errors with multiple "+v+"s").withSubExceptions(exceptions);
}
}

if (count == 0) {
PolyConfig.LOGGER.warn("The regex "+string.getValue()+" didn't match any "+v+"s!");
}
} else {
var id = new Identifier(string.getValue());
var value = registry
.getOrEmpty(id)
.orElseThrow(() -> notFoundInRegistry(id, v));
consumer.accept(id, value, false);
}
}

@FunctionalInterface
public interface GetFromRegistryConsumer<T> {
void accept(Identifier id, T v, boolean isRegex) throws ConfigFormatException;
}

public static KDLValue<?> getSingleArgNoProps(KDLNode node) throws ConfigFormatException {
if (node.getArgs().size() != 1) {
throw wrongAmountOfArgsForNode(node.getArgs().size(), node.getIdentifier());
}
if (!node.getProps().isEmpty()) throw new ConfigFormatException(node.getIdentifier()+" nodes should not have any properties").withHelp("try removing any x=.. attached to the node");
return node.getArgs().get(0);
}

public static ConfigFormatException wrongAmountOfArgsForNode(int v, String nodeName) {
return new ConfigFormatException("Expected 1 argument, found "+v)
.withHelp("`"+nodeName+"` nodes are supposed to only have a single argument, namely the identifier of the modded "+nodeName+".")
.withHelp("There shouldn't be anything else between `"+nodeName+"` and the { or the end of the line");
}

public static ConfigFormatException duplicateEntry(Identifier id) {
return new ConfigFormatException(id+" was already registered");
}

public static ConfigFormatException notFoundInRegistry(Identifier id, String type) {
return new ConfigFormatException("Couldn't find any "+type+" matching "+id)
.withHelp("Try checking the spelling");
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package nl.theepicblock.polyconfig;
package nl.theepicblock.polyconfig.block;

import io.github.theepicblock.polymc.api.block.BlockStateProfile;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package nl.theepicblock.polyconfig;
package nl.theepicblock.polyconfig.block;

import dev.hbeck.kdl.objects.KDLNode;
import io.github.theepicblock.polymc.api.block.BlockStateMerger;
Expand All @@ -7,67 +7,33 @@
import net.minecraft.state.property.Property;
import net.minecraft.util.Identifier;
import net.minecraft.util.registry.Registry;
import nl.theepicblock.polyconfig.Utils;

import java.util.ArrayList;
import java.util.Map;
import java.util.regex.Pattern;

public class BlockNodeParser {
/**
* Interprets a block node
* @param resultMap the map in which the result will be added.
*/
static void parseBlockNode(KDLNode node, Map<Identifier, BlockEntry> resultMap) throws ConfigFormatException {
var args = node.getArgs();
if (args.size() != 1) throw wrongAmountOfArgsForBlockNode(args.size());
if (!node.getProps().isEmpty()) throw new ConfigFormatException("Block nodes should not have any properties").withHelp("try removing any x=.. attached to the block node");

var moddedIdString = args.get(0).getAsString().getValue();
// Try to parse it as a regular id, otherwise consider it a regex
var possibleId = Identifier.tryParse(moddedIdString);
if (possibleId == null) {
var predicate = Pattern.compile(moddedIdString).asMatchPredicate();
var count = 0;
for (var id : Registry.BLOCK.getIds()) {
var exceptions = new ArrayList<ConfigFormatException>();
if (predicate.test(id.toString())) {
count++;
if (!resultMap.containsKey(id)) {
try {
processBlock(id, Registry.BLOCK.get(id), node, resultMap, true);
} catch (ConfigFormatException e) {
exceptions.add(e);
}
}
public static void parseBlockNode(KDLNode node, Map<Identifier, BlockEntry> resultMap) throws ConfigFormatException {
Utils.getFromRegistry(Utils.getSingleArgNoProps(node).getAsString(), "block", Registry.BLOCK, (id, block, isRegex) -> {
if (isRegex) {
if (!resultMap.containsKey(id)) {
processBlock(id, block, node, resultMap, false);
}

if (!exceptions.isEmpty()) {
if (exceptions.size() == 1) {
throw exceptions.get(0);
} else {
throw new ConfigFormatException("Warning: regex caused multiple errors with multiple blocks").withSubExceptions(exceptions);
}
}
}
if (count == 0) {
PolyConfig.LOGGER.warn("The regex "+moddedIdString+" didn't match any blocks!");
}
} else {
var moddedBlock = Registry.BLOCK.getOrEmpty(possibleId).orElseThrow(() -> invalidBlock(possibleId));
if (resultMap.containsKey(possibleId)) {
if (resultMap.get(possibleId).regex()) {
// Silently override
processBlock(possibleId, moddedBlock, node, resultMap, false);
} else {
// If there were two explicit declaration, just crash
throw duplicateEntry(possibleId);
} else {
// Things declared as regexes can be safely overriden
if (resultMap.containsKey(id) && !resultMap.get(id).regex()) {
throw Utils.duplicateEntry(id);
}
processBlock(id, block, node, resultMap, false);
}
}
});
}

static void processBlock(Identifier moddedId, Block moddedBlock, KDLNode node, Map<Identifier, BlockEntry> resultMap, boolean regex) throws ConfigFormatException {
var mergeNodes = KDLUtil.getChildren(node)
private static void processBlock(Identifier moddedId, Block moddedBlock, KDLNode node, Map<Identifier, BlockEntry> resultMap, boolean regex) throws ConfigFormatException {
var mergeNodes = Utils.getChildren(node)
.stream()
.filter(n -> n.getIdentifier().equals("merge"))
.toList();
Expand All @@ -86,7 +52,7 @@ static void processBlock(Identifier moddedId, Block moddedBlock, KDLNode node, M
resultMap.put(moddedId, new BlockEntry(moddedBlock, merger, rootNode, regex));
}

record BlockEntry(Block moddedBlock, BlockStateMerger merger, BlockStateSubgroup rootNode, boolean regex) {}
public record BlockEntry(Block moddedBlock, BlockStateMerger merger, BlockStateSubgroup rootNode, boolean regex) {}

private static BlockStateMerger getMergerFromNode(KDLNode mergeNode, Block block) throws ConfigFormatException {
// A blockstate merger will merge the input blockstate to a canonical version.
Expand Down Expand Up @@ -136,12 +102,6 @@ private static <T extends Comparable<T>> T findCanonicalValue(PropertyFilter.Pro
.orElseThrow(IllegalStateException::new);
}

private static ConfigFormatException wrongAmountOfArgsForBlockNode(int v) {
return new ConfigFormatException("Expected 1 argument, found "+v)
.withHelp("`block` nodes are supposed to only have a single argument, namely the identifier of the modded block.")
.withHelp("There shouldn't be anything else between `block` and the { or the end of the line");
}

private static ConfigFormatException wrongAmountOfArgsForMergeNode(int v) {
return new ConfigFormatException("Expected 1 or zero arguments, found "+v)
.withHelp("`merge` nodes can specify a single freestanding value which is the canonical value for the merge.")
Expand All @@ -157,13 +117,4 @@ private static ConfigFormatException wrongAmountOfPropertiesForMergeNode(int v)
static ConfigFormatException invalidId(String id) {
return new ConfigFormatException("Invalid identifier "+id);
}

static ConfigFormatException invalidBlock(Identifier id) {
return new ConfigFormatException("Couldn't find any block matching "+id)
.withHelp("Try checking the spelling");
}

private static ConfigFormatException duplicateEntry(Identifier id) {
return new ConfigFormatException(id+" was already registered");
}
}
Loading

0 comments on commit c64a00f

Please sign in to comment.