Fancy config groundwork

- Add missing @Nullable annotations to visualizer registry
- Build out common-side configuration infrastructure
- Add fabric config json for vanillin
- Add equivalent forge config json
- Want to allow for toggling specific visualizers/updating config with a
  command
This commit is contained in:
Jozufozu 2025-01-22 14:41:54 -06:00
parent 5472f1a8f9
commit dbd766a5c4
13 changed files with 440 additions and 26 deletions

View file

@ -42,7 +42,7 @@ public interface FlwApiLink {
@Nullable
<T extends Entity> EntityVisualizer<? super T> getVisualizer(EntityType<T> type);
<T extends BlockEntity> void setVisualizer(BlockEntityType<T> type, BlockEntityVisualizer<? super T> visualizer);
<T extends BlockEntity> void setVisualizer(BlockEntityType<T> type, @Nullable BlockEntityVisualizer<? super T> visualizer);
<T extends Entity> void setVisualizer(EntityType<T> type, EntityVisualizer<? super T> visualizer);
<T extends Entity> void setVisualizer(EntityType<T> type, @Nullable EntityVisualizer<? super T> visualizer);
}

View file

@ -43,7 +43,7 @@ public final class VisualizerRegistry {
* @param visualizer The visualizer to set.
* @param <T> The type of the block entity.
*/
public static <T extends BlockEntity> void setVisualizer(BlockEntityType<T> type, BlockEntityVisualizer<? super T> visualizer) {
public static <T extends BlockEntity> void setVisualizer(BlockEntityType<T> type, @Nullable BlockEntityVisualizer<? super T> visualizer) {
FlwApiLink.INSTANCE.setVisualizer(type, visualizer);
}
@ -53,7 +53,7 @@ public final class VisualizerRegistry {
* @param visualizer The visualizer to set.
* @param <T> The type of the entity.
*/
public static <T extends Entity> void setVisualizer(EntityType<T> type, EntityVisualizer<? super T> visualizer) {
public static <T extends Entity> void setVisualizer(EntityType<T> type, @Nullable EntityVisualizer<? super T> visualizer) {
FlwApiLink.INSTANCE.setVisualizer(type, visualizer);
}
}

View file

@ -79,12 +79,12 @@ public class FlwApiLinkImpl implements FlwApiLink {
}
@Override
public <T extends BlockEntity> void setVisualizer(BlockEntityType<T> type, BlockEntityVisualizer<? super T> visualizer) {
public <T extends BlockEntity> void setVisualizer(BlockEntityType<T> type, @Nullable BlockEntityVisualizer<? super T> visualizer) {
VisualizerRegistryImpl.setVisualizer(type, visualizer);
}
@Override
public <T extends Entity> void setVisualizer(EntityType<T> type, EntityVisualizer<? super T> visualizer) {
public <T extends Entity> void setVisualizer(EntityType<T> type, @Nullable EntityVisualizer<? super T> visualizer) {
VisualizerRegistryImpl.setVisualizer(type, visualizer);
}
}

View file

@ -23,11 +23,11 @@ public final class VisualizerRegistryImpl {
return ((EntityTypeExtension<T>) type).flywheel$getVisualizer();
}
public static <T extends BlockEntity> void setVisualizer(BlockEntityType<T> type, BlockEntityVisualizer<? super T> visualizer) {
public static <T extends BlockEntity> void setVisualizer(BlockEntityType<T> type, @Nullable BlockEntityVisualizer<? super T> visualizer) {
((BlockEntityTypeExtension<T>) type).flywheel$setVisualizer(visualizer);
}
public static <T extends Entity> void setVisualizer(EntityType<T> type, EntityVisualizer<? super T> visualizer) {
public static <T extends Entity> void setVisualizer(EntityType<T> type, @Nullable EntityVisualizer<? super T> visualizer) {
((EntityTypeExtension<T>) type).flywheel$setVisualizer(visualizer);
}

View file

@ -0,0 +1,74 @@
package dev.engine_room.vanillin;
import java.util.Objects;
import java.util.function.Predicate;
import org.jetbrains.annotations.Nullable;
import dev.engine_room.flywheel.lib.visualization.SimpleBlockEntityVisualizer;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
public class BlockEntityVisualizerBuilder<T extends BlockEntity> {
private final Configurator configurator;
private final BlockEntityType<T> type;
@Nullable
private SimpleBlockEntityVisualizer.Factory<T> visualFactory;
@Nullable
private Predicate<T> skipVanillaRender;
public BlockEntityVisualizerBuilder(Configurator configurator, BlockEntityType<T> type) {
this.configurator = configurator;
this.type = type;
}
/**
* Sets the visual factory for the block entity.
*
* @param visualFactory The visual factory.
* @return {@code this}
*/
public BlockEntityVisualizerBuilder<T> factory(SimpleBlockEntityVisualizer.Factory<T> visualFactory) {
this.visualFactory = visualFactory;
return this;
}
/**
* Sets a predicate to determine whether to skip rendering with the vanilla {@link BlockEntityRenderer}.
*
* @param skipVanillaRender The predicate.
* @return {@code this}
*/
public BlockEntityVisualizerBuilder<T> skipVanillaRender(Predicate<T> skipVanillaRender) {
this.skipVanillaRender = skipVanillaRender;
return this;
}
/**
* Sets a predicate to never skip rendering with the vanilla {@link BlockEntityRenderer}.
*
* @return {@code this}
*/
public BlockEntityVisualizerBuilder<T> neverSkipVanillaRender() {
this.skipVanillaRender = blockEntity -> false;
return this;
}
/**
* Constructs the block entity visualizer and sets it for the block entity type.
*
* @return The block entity visualizer.
*/
public SimpleBlockEntityVisualizer<T> apply(boolean enabledByDefault) {
Objects.requireNonNull(visualFactory, "Visual factory cannot be null!");
if (skipVanillaRender == null) {
skipVanillaRender = blockEntity -> true;
}
SimpleBlockEntityVisualizer<T> visualizer = new SimpleBlockEntityVisualizer<>(visualFactory, skipVanillaRender);
configurator.register(type, visualizer, enabledByDefault);
return visualizer;
}
}

View file

@ -0,0 +1,82 @@
package dev.engine_room.vanillin;
import java.util.HashMap;
import java.util.Map;
import dev.engine_room.flywheel.api.visualization.BlockEntityVisualizer;
import dev.engine_room.flywheel.api.visualization.EntityVisualizer;
import dev.engine_room.flywheel.api.visualization.VisualizerRegistry;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
public class Configurator {
public final Map<BlockEntityType<?>, ConfiguredBlockEntity<?>> blockEntities = new HashMap<>();
public final Map<EntityType<?>, ConfiguredEntity<?>> entities = new HashMap<>();
public <T extends BlockEntity> void register(BlockEntityType<T> type, BlockEntityVisualizer<? super T> visualizer, boolean enabledByDefault) {
blockEntities.put(type, new ConfiguredBlockEntity<>(type, visualizer, enabledByDefault));
}
public <T extends Entity> void register(EntityType<T> type, EntityVisualizer<? super T> visualizer, boolean enabledByDefault) {
entities.put(type, new ConfiguredEntity<>(type, visualizer, enabledByDefault));
}
public static class ConfiguredBlockEntity<T extends BlockEntity> {
public final BlockEntityType<T> type;
public final BlockEntityVisualizer<? super T> visualizer;
private final boolean enabledByDefault;
private ConfiguredBlockEntity(BlockEntityType<T> type, BlockEntityVisualizer<? super T> visualizer, boolean enabledByDefault) {
this.type = type;
this.visualizer = visualizer;
this.enabledByDefault = enabledByDefault;
}
public String configKey() {
return BuiltInRegistries.BLOCK_ENTITY_TYPE.getKey(type).toString();
}
public boolean enabledByDefault() {
return enabledByDefault;
}
public void set(boolean enabled) {
if (enabled) {
VisualizerRegistry.setVisualizer(type, visualizer);
} else {
VisualizerRegistry.setVisualizer(type, null);
}
}
}
public static class ConfiguredEntity<T extends Entity> {
public final EntityType<T> type;
public final EntityVisualizer<? super T> visualizer;
private final boolean enabledByDefault;
private ConfiguredEntity(EntityType<T> type, EntityVisualizer<? super T> visualizer, boolean enabledByDefault) {
this.type = type;
this.visualizer = visualizer;
this.enabledByDefault = enabledByDefault;
}
public String configKey() {
return BuiltInRegistries.ENTITY_TYPE.getKey(type).toString();
}
public boolean defaultEnabled() {
return enabledByDefault;
}
public void set(boolean enabled) {
if (enabled) {
VisualizerRegistry.setVisualizer(type, visualizer);
} else {
VisualizerRegistry.setVisualizer(type, null);
}
}
}
}

View file

@ -0,0 +1,79 @@
package dev.engine_room.vanillin;
import java.util.Objects;
import java.util.function.Predicate;
import org.jetbrains.annotations.Nullable;
import dev.engine_room.flywheel.lib.visualization.SimpleEntityVisualizer;
import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
/**
* An object to configure the visualizer for an entity.
*
* @param <T> The type of the entity.
*/
public final class EntityVisualizerBuilder<T extends Entity> {
private final Configurator configurator;
private final EntityType<T> type;
@Nullable
private SimpleEntityVisualizer.Factory<T> visualFactory;
@Nullable
private Predicate<T> skipVanillaRender;
public EntityVisualizerBuilder(Configurator configurator, EntityType<T> type) {
this.configurator = configurator;
this.type = type;
}
/**
* Sets the visual factory for the entity.
*
* @param visualFactory The visual factory.
* @return {@code this}
*/
public EntityVisualizerBuilder<T> factory(SimpleEntityVisualizer.Factory<T> visualFactory) {
this.visualFactory = visualFactory;
return this;
}
/**
* Sets a predicate to determine whether to skip rendering with the vanilla {@link EntityRenderer}.
*
* @param skipVanillaRender The predicate.
* @return {@code this}
*/
public EntityVisualizerBuilder<T> skipVanillaRender(Predicate<T> skipVanillaRender) {
this.skipVanillaRender = skipVanillaRender;
return this;
}
/**
* Sets a predicate to always skip rendering with the vanilla {@link EntityRenderer}.
*
* @return {@code this}
*/
public EntityVisualizerBuilder<T> neverSkipVanillaRender() {
this.skipVanillaRender = entity -> false;
return this;
}
/**
* Constructs the entity visualizer and sets it for the entity type.
*
* @return The entity visualizer.
*/
public SimpleEntityVisualizer<T> apply(boolean enabledByDefault) {
Objects.requireNonNull(visualFactory, "Visual factory cannot be null!");
if (skipVanillaRender == null) {
skipVanillaRender = entity -> true;
}
SimpleEntityVisualizer<T> visualizer = new SimpleEntityVisualizer<>(visualFactory, skipVanillaRender);
configurator.register(type, visualizer, enabledByDefault);
return visualizer;
}
}

View file

@ -1,59 +1,73 @@
package dev.engine_room.vanillin.visuals;
import static dev.engine_room.flywheel.lib.visualization.SimpleBlockEntityVisualizer.builder;
import static dev.engine_room.flywheel.lib.visualization.SimpleEntityVisualizer.builder;
package dev.engine_room.vanillin;
import dev.engine_room.vanillin.visuals.BellVisual;
import dev.engine_room.vanillin.visuals.ChestVisual;
import dev.engine_room.vanillin.visuals.MinecartVisual;
import dev.engine_room.vanillin.visuals.ShulkerBoxVisual;
import dev.engine_room.vanillin.visuals.TntMinecartVisual;
import net.minecraft.client.model.geom.ModelLayers;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
public class VanillaVisuals {
public static final Configurator CONFIGURATOR = new Configurator();
public static void init() {
builder(BlockEntityType.CHEST)
.factory(ChestVisual::new)
.apply();
.apply(true);
builder(BlockEntityType.ENDER_CHEST)
.factory(ChestVisual::new)
.apply();
.apply(true);
builder(BlockEntityType.TRAPPED_CHEST)
.factory(ChestVisual::new)
.apply();
.apply(true);
builder(BlockEntityType.BELL)
.factory(BellVisual::new)
.apply();
.apply(true);
builder(BlockEntityType.SHULKER_BOX)
.factory(ShulkerBoxVisual::new)
.apply();
.apply(true);
builder(EntityType.CHEST_MINECART)
.factory((ctx, entity, partialTick) -> new MinecartVisual<>(ctx, entity, partialTick, ModelLayers.CHEST_MINECART))
.skipVanillaRender(MinecartVisual::shouldSkipRender)
.apply();
.apply(true);
builder(EntityType.COMMAND_BLOCK_MINECART)
.factory((ctx, entity, partialTick) -> new MinecartVisual<>(ctx, entity, partialTick, ModelLayers.COMMAND_BLOCK_MINECART))
.skipVanillaRender(MinecartVisual::shouldSkipRender)
.apply();
.apply(true);
builder(EntityType.FURNACE_MINECART)
.factory((ctx, entity, partialTick) -> new MinecartVisual<>(ctx, entity, partialTick, ModelLayers.FURNACE_MINECART))
.skipVanillaRender(MinecartVisual::shouldSkipRender)
.apply();
.apply(true);
builder(EntityType.HOPPER_MINECART)
.factory((ctx, entity, partialTick) -> new MinecartVisual<>(ctx, entity, partialTick, ModelLayers.HOPPER_MINECART))
.skipVanillaRender(MinecartVisual::shouldSkipRender)
.apply();
.apply(true);
builder(EntityType.MINECART)
.factory((ctx, entity, partialTick) -> new MinecartVisual<>(ctx, entity, partialTick, ModelLayers.MINECART))
.skipVanillaRender(MinecartVisual::shouldSkipRender)
.apply();
.apply(true);
builder(EntityType.SPAWNER_MINECART)
.factory((ctx, entity, partialTick) -> new MinecartVisual<>(ctx, entity, partialTick, ModelLayers.SPAWNER_MINECART))
.skipVanillaRender(MinecartVisual::shouldSkipRender)
.apply();
.apply(true);
builder(EntityType.TNT_MINECART)
.factory(TntMinecartVisual::new)
.skipVanillaRender(MinecartVisual::shouldSkipRender)
.apply();
.apply(true);
}
public static <T extends BlockEntity> BlockEntityVisualizerBuilder<T> builder(BlockEntityType<T> type) {
return new BlockEntityVisualizerBuilder<>(CONFIGURATOR, type);
}
public static <T extends Entity> EntityVisualizerBuilder<T> builder(EntityType<T> type) {
return new EntityVisualizerBuilder<>(CONFIGURATOR, type);
}
}

View file

@ -1,5 +1,17 @@
package dev.engine_room.vanillin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.minecraft.resources.ResourceLocation;
public class Vanillin {
public static final String ID = "vanillin";
public static final Logger LOGGER = LoggerFactory.getLogger(ID);
public static final Logger CONFIG_LOGGER = LoggerFactory.getLogger(ID + "/config");
public static ResourceLocation rl(String path) {
return new ResourceLocation(ID, path);
}
}

View file

@ -0,0 +1,78 @@
package dev.engine_room.vanillin;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.annotations.SerializedName;
import net.fabricmc.loader.api.FabricLoader;
public class FabricVanillinConfig {
public static final Path PATH = FabricLoader.getInstance()
.getConfigDir()
.resolve("vanillin.json");
public static final FabricVanillinConfig INSTANCE = new FabricVanillinConfig(PATH.toFile());
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
private final File file;
private Config config;
public FabricVanillinConfig(File file) {
this.file = file;
}
public void load() {
if (file.exists()) {
try (FileReader reader = new FileReader(file)) {
config = GSON.fromJson(reader, Config.class);
} catch (Exception e) {
Vanillin.CONFIG_LOGGER.warn("Could not load config from file '{}'", file.getAbsolutePath(), e);
config = new Config();
}
}
}
public void apply(Configurator configurator) {
for (Configurator.ConfiguredBlockEntity<?> configuredBlockEntity : configurator.blockEntities.values()) {
boolean enabled = config.blockEntities.computeIfAbsent(configuredBlockEntity.configKey(), $ -> configuredBlockEntity.enabledByDefault());
configuredBlockEntity.set(enabled);
}
for (Configurator.ConfiguredEntity<?> configured : configurator.entities.values()) {
boolean enabled = config.entities.computeIfAbsent(configured.configKey(), $ -> configured.defaultEnabled());
configured.set(enabled);
}
}
public void save() {
try (FileWriter writer = new FileWriter(file)) {
GSON.toJson(config, writer);
} catch (Exception e) {
Vanillin.CONFIG_LOGGER.warn("Could not save config to file '{}'", file.getAbsolutePath(), e);
}
}
public static class Config {
@SerializedName("block_entities")
public Map<String, Boolean> blockEntities;
public Map<String, Boolean> entities;
public Config() {
this(new HashMap<>(), new HashMap<>());
}
public Config(Map<String, Boolean> blockEntities, Map<String, Boolean> entities) {
this.blockEntities = blockEntities;
this.entities = entities;
}
}
}

View file

@ -1,11 +1,13 @@
package dev.engine_room.vanillin;
import dev.engine_room.vanillin.visuals.VanillaVisuals;
import net.fabricmc.api.ClientModInitializer;
public class VanillinFabric implements ClientModInitializer {
@Override
public void onInitializeClient() {
VanillaVisuals.init();
FabricVanillinConfig.INSTANCE.load();
FabricVanillinConfig.INSTANCE.apply(VanillaVisuals.CONFIGURATOR);
FabricVanillinConfig.INSTANCE.save();
}
}

View file

@ -0,0 +1,62 @@
package dev.engine_room.vanillin;
import java.util.HashMap;
import java.util.Map;
import net.minecraftforge.common.ForgeConfigSpec;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.config.ModConfig;
public class ForgeVanillinConfig {
public static final ForgeVanillinConfig INSTANCE = new ForgeVanillinConfig(VanillaVisuals.CONFIGURATOR);
public final Map<String, ForgeConfigSpec.BooleanValue> blockEntities = new HashMap<>();
public final Map<String, ForgeConfigSpec.BooleanValue> entities = new HashMap<>();
private final Configurator configurator;
private final ForgeConfigSpec clientSpec;
private ForgeVanillinConfig(Configurator configurator) {
this.configurator = configurator;
var builder = new ForgeConfigSpec.Builder();
builder.push("block_entities");
// Seems like we need to register all field ahead of time so this constructor must run after VanillaVisuals#init
for (var configured : configurator.blockEntities.values()) {
var name = configured.configKey();
var config = builder.define(name, configured.enabledByDefault());
blockEntities.put(name, config);
}
builder.pop();
builder.push("entities");
for (var configured : configurator.entities.values()) {
var name = configured.configKey();
var config = builder.define(name, configured.defaultEnabled());
entities.put(name, config);
}
clientSpec = builder.build();
}
public void apply() {
for (var configured : configurator.blockEntities.values()) {
var value = blockEntities.get(configured.configKey());
if (value != null) {
configured.set(value.get());
}
}
for (var configured : configurator.entities.values()) {
var value = entities.get(configured.configKey());
if (value != null) {
configured.set(value.get());
}
}
}
public void registerSpecs(ModLoadingContext context) {
context.registerConfig(ModConfig.Type.CLIENT, clientSpec);
}
}

View file

@ -1,11 +1,12 @@
package dev.engine_room.vanillin;
import dev.engine_room.vanillin.visuals.VanillaVisuals;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.fml.DistExecutor;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.config.ModConfigEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
@Mod(Vanillin.ID)
@ -14,10 +15,20 @@ public class VanillinForge {
IEventBus forgeEventBus = MinecraftForge.EVENT_BUS;
IEventBus modEventBus = FMLJavaModLoadingContext.get()
.getModEventBus();
DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> clientInit(forgeEventBus, modEventBus));
}
private static void clientInit(IEventBus forgeEventBus, IEventBus modEventBus) {
VanillaVisuals.init();
ForgeVanillinConfig.INSTANCE.registerSpecs(ModLoadingContext.get());
modEventBus.<ModConfigEvent>addListener(event -> {
if (event.getConfig()
.getModId()
.equals(Vanillin.ID)) {
ForgeVanillinConfig.INSTANCE.apply();
}
});
}
}