Skip to content

Commit

Permalink
feat: Pack Config
Browse files Browse the repository at this point in the history
  • Loading branch information
Zepalesque committed Jan 4, 2025
1 parent fbf4a4b commit 74d7768
Show file tree
Hide file tree
Showing 4 changed files with 345 additions and 1 deletion.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ org.gradle.debug=false


# Version
mod_version=1.2.07
mod_version=1.2.08

# Mod
mod_id=zenith
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
package net.zepalesque.zenith.api.packconfig;

import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.AbstractPackResources;
import net.minecraft.server.packs.CompositePackResources;
import net.minecraft.server.packs.PackLocationInfo;
import net.minecraft.server.packs.PackResources;
import net.minecraft.server.packs.PackType;
import net.minecraft.server.packs.repository.Pack;
import net.minecraft.server.packs.resources.IoSupplier;
import net.zepalesque.zenith.core.Zenith;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Supplier;

public class ConfigAssembledPackResources extends AbstractPackResources {

private final Map<Supplier<Boolean>, PackResources> packs;
private final Map<String, Map<Supplier<Boolean>, PackResources>> assetNamespaces;
private final Map<String, Map<Supplier<Boolean>, PackResources>> dataNamespaces;
private final Path source;
private static final Gson GSON = new Gson();
protected ConfigAssembledPackResources(PackLocationInfo id, ImmutableMap<Supplier<Boolean>, PackResources> packs, Path source) {
super(id);
this.source = source;
this.packs = packs;
this.assetNamespaces = this.buildNamespaceMap(PackType.CLIENT_RESOURCES, packs);
this.dataNamespaces = this.buildNamespaceMap(PackType.SERVER_DATA, packs);
}

private Map<String, Map<Supplier<Boolean>, PackResources>> buildNamespaceMap(PackType type, Map<Supplier<Boolean>, PackResources> packMap) {
Map<String, Map<Supplier<Boolean>, PackResources>> map = new HashMap<>();
for (Map.Entry<Supplier<Boolean>, PackResources> entry : packMap.entrySet()) {
if (entry.getValue() != null) {
PackResources pack = entry.getValue();
for (String namespace : pack.getNamespaces(type)) {
map.computeIfAbsent(namespace, k -> new HashMap<>()).put(entry.getKey(), pack);
}
}
}
map.replaceAll((k, list) -> ImmutableMap.copyOf(list));
return ImmutableMap.copyOf(map);
}

@Nullable
@Override
public IoSupplier<InputStream> getRootResource(String... paths) {
Path path = this.resolve(paths);
return !Files.exists(path) ? null : IoSupplier.create(path);
}

protected Path resolve(String... paths) {
Path path = this.source;

for (String name : paths) {
path = path.resolve(name);
}
return path;
}

@Nullable
@Override
public IoSupplier<InputStream> getResource(PackType type, ResourceLocation location) {
if (type == PackType.CLIENT_RESOURCES && location.getPath().matches("lang/.+\\.json")) {
return handleTranslations(location);
} else if (type == PackType.SERVER_DATA) {
if (location.getPath().matches("data_maps/.+\\.json")) return handleDataMaps(location);
else if (location.getPath().matches("tags/.+\\.json")) return handleTags(location);
} else {
for (PackResources pack : this.getCandidatePacks(type, location)) {
IoSupplier<InputStream> ioSupplier = pack.getResource(type, location);
if (ioSupplier != null) {
return ioSupplier;
}
}
}
return null;
}

@Override
public void listResources(PackType type, String resourceNamespace, String paths, ResourceOutput resourceOutput) {
for (PackResources delegate : this.getAvaliablePacks()) {
delegate.listResources(type, resourceNamespace, paths, resourceOutput);
}
}

public List<PackResources> getAvaliablePacks() {
return packs.entrySet().stream().filter(entry -> entry.getKey().get()).map(Map.Entry::getValue).toList();
}

@Override
public @NotNull Set<String> getNamespaces(PackType type) {
return type == PackType.CLIENT_RESOURCES ? assetNamespaces.keySet() : dataNamespaces.keySet();
}

@Override
public void close() {
for (PackResources pack : packs.values()) {
pack.close();
}
}

private List<PackResources> getCandidatePacks(PackType type, ResourceLocation location) {
if (type == PackType.CLIENT_RESOURCES) {
Map<Supplier<Boolean>, PackResources> packsWithNamespace = this.assetNamespaces.get(location.getNamespace());
return packsWithNamespace == null ? Collections.emptyList() : packsWithNamespace.entrySet().stream().filter(entry -> entry.getKey().get()).map(Map.Entry::getValue).toList();
} else {
Map<Supplier<Boolean>, PackResources> packsWithNamespace = this.dataNamespaces.get(location.getNamespace());
return packsWithNamespace == null ? Collections.emptyList() : packsWithNamespace.entrySet().stream().filter(entry -> entry.getKey().get()).map(Map.Entry::getValue).toList();
}
}

public String toString() {
return String.format("%s: %s", this.getClass().getName(), source);
}



public record AssembledResourcesSupplier(ImmutableMap<Supplier<Boolean>, PackResources> packs, Path source) implements Pack.ResourcesSupplier {
@Override
public PackResources openPrimary(PackLocationInfo location) {
return new ConfigAssembledPackResources(location, this.packs, this.source);
}

@Override
public PackResources openFull(PackLocationInfo location, Pack.Metadata info) {
PackResources packresources = this.openPrimary(location);
List<String> list = info.overlays();
if (list.isEmpty()) {
return packresources;
} else {
List<PackResources> list1 = new ArrayList<>(list.size());

for (String s : list) {
Path path = this.source.resolve(s);
list1.add(new ConfigAssembledPackResources(location, this.packs, path));
}

return new CompositePackResources(packresources, list1);
}
}
}

protected IoSupplier<InputStream> handleTranslations(ResourceLocation location) {
return handleConflicts(PackType.CLIENT_RESOURCES, location, (combined, addend) -> addend.entrySet().forEach(entry -> combined.add(entry.getKey(), entry.getValue())));
}

protected IoSupplier<InputStream> handleTags(ResourceLocation location) {
return handleConflicts(PackType.SERVER_DATA, location, (combined, addend) -> {
for (String array : new String[] {"remove", "values"}) {
if (addend.has(array)) {
JsonArray addendValues = addend.get(array).getAsJsonArray();
if (combined.has(array)) {
combined.add(array, new JsonArray());
}
JsonArray combinedValues = combined.get(array).getAsJsonArray();

addendValues.asList().forEach(combinedValues::add);
}
}
});
}

protected IoSupplier<InputStream> handleDataMaps(ResourceLocation location) {
return handleConflicts(PackType.SERVER_DATA, location, (combined, addend) -> {
for (String array : new String[] {"remove", "values"}) {
if (addend.has(array)) {
JsonObject addendValues = addend.get(array).getAsJsonObject();
if (combined.has(array)) {
combined.add(array, new JsonArray());
}
JsonObject combinedValues = combined.get(array).getAsJsonObject();

addendValues.entrySet().forEach(entry -> combinedValues.add(entry.getKey(), entry.getValue()));
}
}
});
}

protected IoSupplier<InputStream> handleConflicts(PackType type, ResourceLocation location, BiConsumer<JsonObject, JsonObject> combiner) {
JsonObject combined = new JsonObject();
for (PackResources pack : getCandidatePacks(type, location)) {
IoSupplier<InputStream> ioSupplier = pack.getResource(type, location);
if (ioSupplier != null) {
try {
JsonObject jsonobject = GSON.fromJson(new InputStreamReader(ioSupplier.get(), StandardCharsets.UTF_8), JsonObject.class);
combiner.accept(combined, jsonobject);
} catch (Exception e) {
Zenith.LOGGER.error("Caught exception when trying to combine pack config resource files!", e);
}
}
}

if (combined.entrySet().isEmpty()) {
return null;
}
String input = GSON.toJson(combined);
return () -> new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8));
}
}
75 changes: 75 additions & 0 deletions src/main/java/net/zepalesque/zenith/api/packconfig/PackConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package net.zepalesque.zenith.api.packconfig;

import com.google.common.collect.ImmutableMap;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.PackLocationInfo;
import net.minecraft.server.packs.PackResources;
import net.minecraft.server.packs.PackType;
import net.minecraft.server.packs.PathPackResources;
import net.minecraft.server.packs.repository.PackSource;
import net.neoforged.fml.ModList;
import net.neoforged.neoforge.common.ModConfigSpec;
import net.neoforged.neoforge.event.AddPackFindersEvent;
import net.zepalesque.zenith.core.Zenith;

import java.nio.file.Path;
import java.util.HashMap;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.function.Supplier;

public class PackConfig {

private final ResourceLocation id;
private final PackType type;
private final HashMap<Supplier<Boolean>, PackResources> resources = new HashMap<>();
private final String folder;
private boolean locked = false;

public PackConfig(ResourceLocation id, PackType type) {
this.id = id;
this.type = type;
this.folder = switch (type) {
case SERVER_DATA -> "data/";
case CLIENT_RESOURCES -> "resource/";
};
}

public ConfigAssembledPackResources.AssembledResourcesSupplier generate(Path path) {
ImmutableMap<Supplier<Boolean>, PackResources> builder = ImmutableMap.copyOf(resources);
locked = true;
return new ConfigAssembledPackResources.AssembledResourcesSupplier(builder, path);
}

public PathPackResources createPack(String path, String id) {
Path resource = ModList.get().getModFileById(this.id.getNamespace()).getFile().findResource("packs/" + path + id);
PackLocationInfo loc = new PackLocationInfo(id, Component.empty(), PackSource.BUILT_IN, Optional.empty());
return new PathPackResources(loc, resource);
}

public <B, T extends ModConfigSpec.ConfigValue<B>> T register(T config, String path, String id, Predicate<B> predicate) {
if (!locked) {
resources.putIfAbsent(() -> predicate.test(config.get()), createPack(path, id));
Zenith.LOGGER.info("Registered config {}{} for pack {}...", path, id, this.id);
} else {
Zenith.LOGGER.warn("Attempted to register pack config for pack {}{} after locking was already complete!", path, id);
}
return config;
}

public <T extends ModConfigSpec.ConfigValue<Boolean>> T register(T config, String path, String id, boolean predicate) {
return register(config, path, id, bool -> bool == predicate);
}

public <T extends ModConfigSpec.ConfigValue<Boolean>> T register(T config, String path, String id) {
return register(config, path, id, true);
}


public void setup(AddPackFindersEvent event) {
if (event.getPackType() == this.type) {
PackUtils.setupPack(event, this.id, this.folder, true, this::generate);
}
}
}
51 changes: 51 additions & 0 deletions src/main/java/net/zepalesque/zenith/api/packconfig/PackUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package net.zepalesque.zenith.api.packconfig;

import net.minecraft.SharedConstants;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.PackLocationInfo;
import net.minecraft.server.packs.PackSelectionConfig;
import net.minecraft.server.packs.PackType;
import net.minecraft.server.packs.PathPackResources;
import net.minecraft.server.packs.metadata.pack.PackMetadataSection;
import net.minecraft.server.packs.repository.Pack;
import net.minecraft.server.packs.repository.PackCompatibility;
import net.minecraft.server.packs.repository.PackSource;
import net.minecraft.world.flag.FeatureFlagSet;
import net.neoforged.fml.ModList;
import net.neoforged.neoforge.event.AddPackFindersEvent;

import java.nio.file.Path;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;

public class PackUtils {

public static void setupPack(AddPackFindersEvent event, String modid, String path, String id, boolean required, Function<Path, Pack.ResourcesSupplier> packBuilder) {
PackLocationInfo loc = new PackLocationInfo(id, Component.translatable("pack." + modid + "." + id + ".title"), PackSource.BUILT_IN, Optional.empty());
String folder = (event.getPackType() == PackType.SERVER_DATA ? "data/" : "resource/");
Path resourcePath = ModList.get().getModFileById(modid).getFile().findResource("packs/" + folder + path);
PackMetadataSection metadata = new PackMetadataSection(Component.translatable("pack." + modid + "." + id + ".description"),
SharedConstants.getCurrentVersion().getPackVersion(event.getPackType()));
Pack.Metadata meta = new Pack.Metadata(metadata.description(), PackCompatibility.COMPATIBLE, FeatureFlagSet.of(), List.of(), true);
Pack.ResourcesSupplier resources = packBuilder.apply(resourcePath);
event.addRepositorySource((source) ->
source.accept(new Pack(
loc,
resources,
meta,
new PackSelectionConfig(required, Pack.Position.TOP, false))
));

}

public static void setupPack(AddPackFindersEvent event, String modid, String path, String id, boolean required) {
setupPack(event, modid, path, id, required, PathPackResources.PathResourcesSupplier::new);
}

public static void setupPack(AddPackFindersEvent event, ResourceLocation location, String folder, boolean required, Function<Path, Pack.ResourcesSupplier> packBuilder) {
String path = location.getPath();
setupPack(event, location.getNamespace(), folder + path, path, required, packBuilder);
}
}

0 comments on commit 74d7768

Please sign in to comment.