Skip to content

Commit

Permalink
Datagen for and loading of structure-specific Loonium configurations
Browse files Browse the repository at this point in the history
(includes a generic reloadable config data manager, which currently only covers Loonium configurations)
  • Loading branch information
TheRealWormbo committed May 10, 2024
1 parent 7f0f8e5 commit 3e3594d
Show file tree
Hide file tree
Showing 7 changed files with 199 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
import vazkii.botania.common.brew.BotaniaBrews;
import vazkii.botania.common.brew.BotaniaMobEffects;
import vazkii.botania.common.command.SkyblockCommand;
import vazkii.botania.common.config.ConfigDataManager;
import vazkii.botania.common.crafting.BotaniaRecipeTypes;
import vazkii.botania.common.entity.BotaniaEntities;
import vazkii.botania.common.entity.GaiaGuardianEntity;
Expand Down Expand Up @@ -138,6 +139,7 @@ public void onInitialize() {
PatchouliAPI.get().registerMultiblock(prefix("gaia_ritual"), GaiaGuardianEntity.ARENA_MULTIBLOCK.get());

OrechidManager.registerListener();
ConfigDataManager.registerListener();
CraftyCrateBlockEntity.registerListener();
CorporeaNodeDetectors.register(new FabricTransferCorporeaNodeDetector());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
import vazkii.botania.common.brew.BotaniaMobEffects;
import vazkii.botania.common.brew.effect.SoulCrossMobEffect;
import vazkii.botania.common.command.SkyblockCommand;
import vazkii.botania.common.config.ConfigDataManager;
import vazkii.botania.common.crafting.BotaniaRecipeTypes;
import vazkii.botania.common.entity.BotaniaEntities;
import vazkii.botania.common.entity.GaiaGuardianEntity;
Expand Down Expand Up @@ -166,6 +167,7 @@ public void commonSetup(FMLCommonSetupEvent evt) {
PatchouliAPI.get().registerMultiblock(prefix("gaia_ritual"), GaiaGuardianEntity.ARENA_MULTIBLOCK.get());

OrechidManager.registerListener();
ConfigDataManager.registerListener();
CraftyCrateBlockEntity.registerListener();
CorporeaNodeDetectors.register(new ForgeCapCorporeaNodeDetector());
if (ModList.get().isLoaded("inventorysorter")) {
Expand Down
9 changes: 9 additions & 0 deletions Xplat/src/main/java/vazkii/botania/api/BotaniaAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import vazkii.botania.api.corporea.CorporeaNodeDetector;
import vazkii.botania.api.internal.DummyManaNetwork;
import vazkii.botania.api.internal.ManaNetwork;
import vazkii.botania.common.config.ConfigDataManager;

import java.util.Collections;
import java.util.Map;
Expand Down Expand Up @@ -216,4 +217,12 @@ default void sparkleFX(Level world, double x, double y, double z, float r, float
default void registerCorporeaNodeDetector(CorporeaNodeDetector detector) {

}

default ConfigDataManager getConfigData() {
return null;
}

default void setConfigData(ConfigDataManager configDataManager) {

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,35 +20,42 @@
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.level.levelgen.structure.StructureSpawnOverride;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import vazkii.botania.api.BotaniaAPI;

import java.util.List;
import java.util.Optional;
import java.util.function.Function;

public class LooniumStructureConfiguration {
public static final Codec<LooniumStructureConfiguration> CODEC = RecordCodecBuilder.create(
instance -> instance.group(
ExtraCodecs.POSITIVE_INT.fieldOf("manaCost").forGetter(o -> o.manaCost),
StructureSpawnOverride.BoundingBoxType.CODEC.fieldOf("boundingBoxType").forGetter(o -> o.boundingBoxType),
WeightedRandomList.codec(MobSpawnData.CODEC).fieldOf("spawnedMobs").forGetter(o -> o.spawnedMobs),
Codec.list(MobAttributeModifier.CODEC).fieldOf("attributeModifiers").forGetter(o -> o.attributeModifiers),
Codec.list(MobEffectToApply.CODEC).fieldOf("effectsToApply").forGetter(o -> o.effectsToApply)
).apply(instance, LooniumStructureConfiguration::new)
);
public static final Codec<LooniumStructureConfiguration> OPTIONAL_CODEC = RecordCodecBuilder.create(
instance -> instance.group(
ResourceLocation.CODEC.fieldOf("parent").forGetter(o -> o.parent),
ExtraCodecs.POSITIVE_INT.optionalFieldOf("manaCost").forGetter(o -> Optional.ofNullable(o.manaCost)),
StructureSpawnOverride.BoundingBoxType.CODEC.optionalFieldOf("boundingBoxType")
.forGetter(o -> Optional.ofNullable(o.boundingBoxType)),
WeightedRandomList.codec(MobSpawnData.CODEC).optionalFieldOf("spawnedMobs")
.forGetter(o -> Optional.ofNullable(o.spawnedMobs)),
Codec.list(MobAttributeModifier.CODEC).optionalFieldOf("attributeModifiers")
.forGetter(o -> Optional.ofNullable(o.attributeModifiers)),
Codec.list(MobEffectToApply.CODEC).optionalFieldOf("effectsToApply")
.forGetter(o -> Optional.ofNullable(o.effectsToApply))
).apply(instance, LooniumStructureConfiguration::new)
);
public static final Codec<LooniumStructureConfiguration> CODEC = ExtraCodecs.validate(
RecordCodecBuilder.create(
instance -> instance.group(
ResourceLocation.CODEC.optionalFieldOf("parent")
.forGetter(lsc -> Optional.ofNullable(lsc.parent)),
ExtraCodecs.POSITIVE_INT.optionalFieldOf("manaCost")
.forGetter(lsc -> Optional.ofNullable(lsc.manaCost)),
StructureSpawnOverride.BoundingBoxType.CODEC.optionalFieldOf("boundingBoxType")
.forGetter(lsc -> Optional.ofNullable(lsc.boundingBoxType)),
WeightedRandomList.codec(MobSpawnData.CODEC).optionalFieldOf("spawnedMobs")
.forGetter(lsc -> Optional.ofNullable(lsc.spawnedMobs)),
Codec.list(MobAttributeModifier.CODEC).optionalFieldOf("attributeModifiers")
.forGetter(lsc -> Optional.ofNullable(lsc.attributeModifiers)),
Codec.list(MobEffectToApply.CODEC).optionalFieldOf("effectsToApply")
.forGetter(lsc -> Optional.ofNullable(lsc.effectsToApply))
).apply(instance, LooniumStructureConfiguration::new)
), lsc -> {
if (lsc.parent == null && (lsc.manaCost == null || lsc.boundingBoxType == null || lsc.spawnedMobs == null)) {
return DataResult.error(() -> "Mana cost, bounding box type, and spawned mobs must be specified if there is no parent configuration");
}
if (lsc.spawnedMobs != null && lsc.spawnedMobs.isEmpty()) {
return DataResult.error(() -> "Spawned mobs cannot be empty");
}
return DataResult.success(lsc);
});
public static final ResourceLocation DEFAULT_CONFIG_ID = new ResourceLocation(BotaniaAPI.MODID, "default");

public final Integer manaCost;
public final StructureSpawnOverride.BoundingBoxType boundingBoxType;
Expand All @@ -74,20 +81,34 @@ public LooniumStructureConfiguration(ResourceLocation parent, @Nullable Integer
this.parent = parent;
}

private LooniumStructureConfiguration(ResourceLocation parent, Optional<Integer> manaCost,
private LooniumStructureConfiguration(Optional<ResourceLocation> parent, Optional<Integer> manaCost,
Optional<StructureSpawnOverride.BoundingBoxType> boundingBoxType,
Optional<WeightedRandomList<MobSpawnData>> spawnedMobs,
Optional<List<MobAttributeModifier>> attributeModifiers,
Optional<List<MobEffectToApply>> effectsToApply) {
this(manaCost.orElse(null), boundingBoxType.orElse(null), spawnedMobs.orElse(null),
attributeModifiers.orElse(null), effectsToApply.orElse(null));
this(parent.orElse(null), manaCost.orElse(null), boundingBoxType.orElse(null),
spawnedMobs.orElse(null), attributeModifiers.orElse(null), effectsToApply.orElse(null));
}

public LooniumStructureConfiguration(ResourceLocation parent,
StructureSpawnOverride.BoundingBoxType boundingBoxType) {
this(parent, null, boundingBoxType, null, null, null);
}

public LooniumStructureConfiguration getEffectiveConfig(
Function<ResourceLocation, LooniumStructureConfiguration> parentSupplier) {
if (parent == null) {
return this;
}
var parentConfig = parentSupplier.apply(parent).getEffectiveConfig(parentSupplier);

return new LooniumStructureConfiguration(manaCost != null ? manaCost : parentConfig.manaCost,
boundingBoxType != null ? boundingBoxType : parentConfig.boundingBoxType,
spawnedMobs != null ? spawnedMobs : parentConfig.spawnedMobs,
attributeModifiers != null ? attributeModifiers : parentConfig.attributeModifiers,
effectsToApply != null ? effectsToApply : parentConfig.effectsToApply);
}

public static class MobSpawnData extends WeightedEntry.IntrusiveBase {
public static final Codec<MobSpawnData> CODEC = RecordCodecBuilder.create(
instance -> instance.group(
Expand Down Expand Up @@ -216,6 +237,11 @@ private MobEffectToApply(MobEffect effect, Optional<Integer> optionalDuration, O
this(effect, optionalDuration.orElse(MobEffectInstance.INFINITE_DURATION), optionalAmplifier.orElse(0));
}

@NotNull
public MobEffectInstance createMobEffectInstance() {
return new MobEffectInstance(effect, duration, amplifier);
}

private Optional<Integer> getOptionalDuration() {
return duration != MobEffectInstance.INFINITE_DURATION ? Optional.of(duration) : Optional.empty();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package vazkii.botania.common.config;

import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.mojang.serialization.Codec;
import com.mojang.serialization.JsonOps;

import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.PackType;
import net.minecraft.server.packs.resources.PreparableReloadListener;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener;
import net.minecraft.util.profiling.ProfilerFiller;

import org.jetbrains.annotations.NotNull;

import vazkii.botania.api.BotaniaAPI;
import vazkii.botania.api.configdata.LooniumStructureConfiguration;
import vazkii.botania.xplat.XplatAbstractions;

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

import static vazkii.botania.common.lib.ResourceLocationHelper.prefix;

public class ConfigDataManager implements PreparableReloadListener {
public static void registerListener() {
XplatAbstractions.INSTANCE.registerReloadListener(PackType.SERVER_DATA, prefix("configdata"), new ConfigDataManager());
}

private final Map<ResourceLocation, LooniumStructureConfiguration> looniumConfigs = new HashMap<>();

public LooniumStructureConfiguration getEffectiveLooniumStructureConfiguration(ResourceLocation id) {
LooniumStructureConfiguration configuration = this.looniumConfigs.get(id);
return configuration != null ? configuration.getEffectiveConfig(looniumConfigs::get) : null;
}

private static void validateLooniumConfig(Map<ResourceLocation, LooniumStructureConfiguration> map) {
Set<ResourceLocation> errorEntries = new HashSet<>();
Set<ResourceLocation> visitedEntries = new LinkedHashSet<>();
do {
errorEntries.clear();
for (var entry : map.entrySet()) {
ResourceLocation id = entry.getKey();
ResourceLocation parent = entry.getValue().parent;
if (id.equals(parent)) {
BotaniaAPI.LOGGER.warn("Ignoring Loonium structure configuration, because it specified itself as parent: {}", id);
errorEntries.add(id);
} else {
visitedEntries.clear();
if (!findTopmostParent(map, id, parent, visitedEntries)) {
BotaniaAPI.LOGGER.warn("Ignoring Loonium structure configuration(s) without top-most parent: {}", visitedEntries);
errorEntries.addAll(visitedEntries);
break;
}
}
}
errorEntries.forEach(map::remove);
} while (!errorEntries.isEmpty() && !map.isEmpty());

if (!map.containsKey(LooniumStructureConfiguration.DEFAULT_CONFIG_ID)) {
BotaniaAPI.LOGGER.error("Default Loonium configuration not found!");
}
}

private static boolean findTopmostParent(Map<ResourceLocation, LooniumStructureConfiguration> map,
ResourceLocation id, ResourceLocation parent, Set<ResourceLocation> visitedEntries) {
if (!visitedEntries.add(id)) {
BotaniaAPI.LOGGER.warn("Cyclic dependency between Loonium structure configurations detected: {}", visitedEntries);
return false;
}
if (parent == null) {
return true;
}
var parentConfig = map.get(parent);
return parentConfig != null && findTopmostParent(map, parent, parentConfig.parent, visitedEntries);
}

private void applyLooniumConfig(Map<ResourceLocation, LooniumStructureConfiguration> looniumConfigs) {
this.looniumConfigs.putAll(looniumConfigs);
}

@NotNull
@Override
public CompletableFuture<Void> reload(@NotNull PreparationBarrier barrier, @NotNull ResourceManager manager,
@NotNull ProfilerFiller prepProfiler, @NotNull ProfilerFiller reloadProfiler,
@NotNull Executor backgroundExecutor, @NotNull Executor gameExecutor) {
var looniumTask = scheduleConfigParse(barrier, manager, backgroundExecutor, gameExecutor, ConfigDataType.LOONUIM);

return CompletableFuture.allOf(looniumTask).thenRun(() -> BotaniaAPI.instance().setConfigData(this));
}

private <T> CompletableFuture<Void> scheduleConfigParse(PreparationBarrier barrier, ResourceManager manager,
Executor backgroundExecutor, Executor gameExecutor, ConfigDataType<T> type) {
return CompletableFuture.supplyAsync(() -> {
Map<ResourceLocation, JsonElement> resourceMap = new HashMap<>();
SimpleJsonResourceReloadListener.scanDirectory(manager, "config/" + type.directory, new Gson(), resourceMap);
Map<ResourceLocation, T> configs = new HashMap<>(resourceMap.size());
resourceMap.forEach((id, jsonElement) -> type.codec.parse(JsonOps.INSTANCE, jsonElement).result().ifPresent(c -> configs.put(id, c)));
type.validateFunction.accept(configs);
return configs;
}, backgroundExecutor)
.thenCompose(barrier::wait)
.thenAcceptAsync(c -> type.applyFunction.accept(this, c), gameExecutor);
}

private record ConfigDataType<T> (Codec<T> codec, String directory,
Consumer<Map<ResourceLocation, T>> validateFunction,
BiConsumer<ConfigDataManager, Map<ResourceLocation, T>> applyFunction) {
private static final ConfigDataType<LooniumStructureConfiguration> LOONUIM =
new ConfigDataType<>(LooniumStructureConfiguration.CODEC, "loonium",
ConfigDataManager::validateLooniumConfig, ConfigDataManager::applyLooniumConfig);

}
}
13 changes: 13 additions & 0 deletions Xplat/src/main/java/vazkii/botania/common/impl/BotaniaAPIImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import vazkii.botania.api.internal.ManaNetwork;
import vazkii.botania.client.fx.SparkleParticleData;
import vazkii.botania.common.block.flower.functional.SolegnoliaBlockEntity;
import vazkii.botania.common.config.ConfigDataManager;
import vazkii.botania.common.handler.BotaniaSounds;
import vazkii.botania.common.handler.EquipmentHandler;
import vazkii.botania.common.handler.ManaNetworkHandler;
Expand Down Expand Up @@ -202,6 +203,8 @@ public Ingredient getRepairIngredient() {
}
}

private ConfigDataManager configDataManager = new ConfigDataManager();

@Override
public int apiVersion() {
return 2;
Expand Down Expand Up @@ -291,4 +294,14 @@ public void registerPaintableBlock(ResourceLocation block, Function<DyeColor, Bl
public void registerCorporeaNodeDetector(CorporeaNodeDetector detector) {
CorporeaNodeDetectors.register(detector);
}

@Override
public ConfigDataManager getConfigData() {
return configDataManager;
}

@Override
public void setConfigData(ConfigDataManager configDataManager) {
this.configDataManager = configDataManager;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
import java.util.Map;
import java.util.concurrent.CompletableFuture;

import static vazkii.botania.common.lib.ResourceLocationHelper.prefix;
import static vazkii.botania.data.LooniumStructureLootProvider.getStructureId;

public class LooniumStructureConfigurationProvider implements DataProvider {
Expand All @@ -50,7 +49,7 @@ public CompletableFuture<?> run(@NotNull CachedOutput cache) {
CompoundTag chargedCreeperNbt = new CompoundTag();
chargedCreeperNbt.putBoolean("powered", true);

ResourceLocation defaultConfigId = prefix("default");
ResourceLocation defaultConfigId = LooniumStructureConfiguration.DEFAULT_CONFIG_ID;
configs.put(defaultConfigId, new LooniumStructureConfiguration(
35000, StructureSpawnOverride.BoundingBoxType.PIECE,
WeightedRandomList.create(
Expand Down Expand Up @@ -101,11 +100,8 @@ public CompletableFuture<?> run(@NotNull CachedOutput cache) {
for (var e : configs.entrySet()) {
Path path = pathProvider.json(e.getKey());
var config = e.getValue();
var jsonTree = (config.parent != null
? LooniumStructureConfiguration.OPTIONAL_CODEC
: LooniumStructureConfiguration.CODEC)
.encodeStart(JsonOps.INSTANCE, config)
.getOrThrow(false, BotaniaAPI.LOGGER::error);
var jsonTree = LooniumStructureConfiguration.CODEC.encodeStart(JsonOps.INSTANCE, config)
.getOrThrow(false, BotaniaAPI.LOGGER::error);
output.add(DataProvider.saveStable(cache, jsonTree, path));
}
return CompletableFuture.allOf(output.toArray(CompletableFuture[]::new));
Expand Down

0 comments on commit 3e3594d

Please sign in to comment.