Skip to content

Commit

Permalink
V2.3.0 (#10)
Browse files Browse the repository at this point in the history
- Multiple modules are allowed per item.
- Added the module `enchantment` for tools and armours.
- `custom_data` may now search nested pathes.
- `custom_name` will now convert all names into valid identifiers.
  • Loading branch information
Estecka authored Sep 5, 2024
1 parent 8eab070 commit 1d62e2d
Show file tree
Hide file tree
Showing 14 changed files with 326 additions and 74 deletions.
8 changes: 8 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,11 @@
- Special modules no longer need to register a constructor. They may be registered the same way as Simple modules.
- Special modules are no longer required to implement their variant logic separately from the special logic.
- The old ways of registering special and parameterized modules has been marked as deprecated.
## 2.3
- It's now possible to apply multiple modules to a single item.
- It's now possible to apply a single module to multiple items.
- Added the module `enchantment` for tools and armours.
- `stored_enchantments` (plural) is being renamed to `stored_enchantment` (singular).
- `custom_data` may now look for variants inside nested pathes. The parameter `nbtKey` is being renamed to `nbtPath`.
- `custom_name` will now always convert all names into valid identifiers. The case sensitivity option was removed.
- `custom_name`'s special names are now case-sensitive.
26 changes: 15 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,37 @@
A CIT logic for MC 1.21, optimized around items with standardized variants.

**This mod is not a plug-and-play replacement for Optifine/CIT-resewn;** it uses its own resource format. Changes to older packs are required for them to work.
On low-end PCs, using this mod instead of the optifine format may lead to improved performances in scenarios where a single item has very many different variants.

The mod is not as all-purpose as optifine, it still requires specialized code for most item type, but this code is modular and easy to expand upon. Other mods can also add custom modules for their own items.
The mod contains built-in logic for handling **Axolotl Buckets**, **Enchantments**, **Music Discs**, **Goat Horns**, and **Potions**.
There are also more generic modules that can identify a variant from the `custom_data` or `custom_name` component of an item. Other mods can easily create custom logic for their own items.

Built-in modules support **Axolotl Buckets**, **Enchanted Books**, **Music Discs**, **Goat Horns**, and **Potions**.
There are also more generic modules that can identify a variant from the `custom_data` or `custom_name` component of an item.
If Mojang ever makes these items more componentized, you can expect Banner Patterns, Trim Templates, and Pottery Sherds to become supported in the future.
If Mojang ever makes these items data-driven, you can expect Banner Patterns, Trim Templates, and Pottery Sherds to become supported in the future.

## Difference with Optifine/CIT-Resewn
The base concept for this mod was born out of a need for _optimization_, at a time when CIT-resewn was still an up-to-date option.
This comes at the cost of some flexibility; while being _multi_-purpose, it may not be as _all_-purpose as optifine.

This mod excels in scenarios where a single item type has a large amount of variants, which can all be derived from a single piece of data. Instead of defining a condition for every single CIT, you define a single rule that governs all CITs in a collection (so-called modules).

## Resource Pack Format
The format revolves around item variants (reduced to namespaced identifiers) being automatically associated to [item models](https://minecraft.wiki/w/Model#Item_models) with matching names, stored in a directory of your choosing.
This is an overview, see the [wiki](/~https://github.com/Estecka/mc-Variants-CIT/wiki) for a complete guide.

The format revolves around item **variants** (reduced to namespaced identifiers) being automatically associated to [item models](https://minecraft.wiki/w/Model#Item_models) with matching names, stored in a directory of your choosing.

Resource packs must start by providing a configuration file, that defines what item type is affected, how to figure out its variant, and where the models are located.
Resource packs must start by providing a module configuration, that defines what item is affected, how to figure out its variant, and where their models are located.

For example, here's a module that would reproduce the behaviour of the previous version of the mod, Enchants-CIT :
`/assets/minecraft/variant-cits/item/enchanted_book.json`
```json
{
"type": "stored_enchantments",
"type": "stored_enchantment",
"items": ["enchanted_book"],
"modelPrefix": "item/enchanted_book/",
"special": {
"multi": "enchants-cit:item/multi_enchanted_book"
}
}
```
The targeted item type is automatically derived from the file name of the config.
Here, the enchantment `minecraft:unbreaking` will be associated with the model stored at `/assets/minecraft/models/item/enchanted_book/unbreaking.json`

Some module types may define additional models to use in special cases, or take addional parameters.
See the [wiki](/~https://github.com/Estecka/mc-Variants-CIT/wiki) for a more complete guide.
Some module types may define additional models to use in special cases, or take addional parameters.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ loader_version=0.16.2
fabric_version=0.102.1+1.21.1

# Mod Properties
mod_version=2.2.0
mod_version=2.3.0
maven_group=fr.estecka.variantscit
archives_base_name=variants-cit
26 changes: 26 additions & 0 deletions src/main/java/fr/estecka/variantscit/IItemModelProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package fr.estecka.variantscit;

import java.util.List;
import net.minecraft.client.util.ModelIdentifier;
import net.minecraft.item.ItemStack;

public interface IItemModelProvider
{
ModelIdentifier GetModelForItem(ItemStack stack);

static public IItemModelProvider OfList(List<? extends IItemModelProvider> providers){
if (providers.isEmpty())
return __->null;
else if (providers.size() == 1)
return providers.get(0);

return (ItemStack stack)->{
ModelIdentifier id;
for (var p : providers)
if (null != (id=p.GetModelForItem(stack)))
return id;

return null;
};
}
}
12 changes: 11 additions & 1 deletion src/main/java/fr/estecka/variantscit/ModuleDefinition.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package fr.estecka.variantscit;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.jetbrains.annotations.Nullable;
Expand All @@ -13,11 +14,20 @@
import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;

public record ModuleDefinition(Identifier type, String modelPrefix, Optional<Identifier> fallbackModel, Map<String,Identifier> specialModels)
public record ModuleDefinition(
Identifier type,
Optional<List<Identifier>> targets,
int priority,
String modelPrefix,
Optional<Identifier> fallbackModel,
Map<String,Identifier> specialModels
)
{
static public final MapCodec<ModuleDefinition> CODEC = RecordCodecBuilder.<ModuleDefinition>mapCodec(builder->builder
.group(
Identifier.CODEC.fieldOf("type").forGetter(ModuleDefinition::type),
Identifier.CODEC.listOf().optionalFieldOf("items").forGetter(ModuleDefinition::targets),
Codec.INT.fieldOf("priority").orElse(0).forGetter(ModuleDefinition::priority),
Codec.STRING.validate(ModuleDefinition::ValidatePath).fieldOf("modelPrefix").forGetter(ModuleDefinition::modelPrefix),
Identifier.CODEC.optionalFieldOf("fallback").forGetter(ModuleDefinition::fallbackModel),
Codec.unboundedMap(Codec.STRING, Identifier.CODEC).fieldOf("special").orElse(ImmutableMap.<String,Identifier>of()).forGetter(ModuleDefinition::specialModels)
Expand Down
107 changes: 85 additions & 22 deletions src/main/java/fr/estecka/variantscit/ModuleLoader.java
Original file line number Diff line number Diff line change
@@ -1,67 +1,130 @@
package fr.estecka.variantscit;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import org.jetbrains.annotations.Nullable;
import com.google.gson.JsonObject;
import com.ibm.icu.impl.Pair;
import com.mojang.serialization.DataResult;
import com.mojang.serialization.JsonOps;
import net.fabricmc.fabric.api.client.model.loading.v1.PreparableModelLoadingPlugin.DataLoader;
import net.minecraft.item.Item;
import net.minecraft.registry.Registries;
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.resource.Resource;
import net.minecraft.resource.ResourceManager;
import net.minecraft.util.Identifier;
import net.minecraft.util.JsonHelper;

public class ModuleLoader
implements DataLoader<Map<Item,VariantManager>>
public final class ModuleLoader
implements DataLoader<ModuleLoader.Result>
{

static public class Result {
public final Map<RegistryEntry<Item>,List<MetaModule>> modulesPerItem = new HashMap<>();
}

/**
* Contains all the data pertaining to a module file. Most of this data is
* only relevant to the resource-loading phase, and will be discarded
* afterward.
*/
static public class MetaModule {
public final Identifier id;
public final VariantManager manager;
public final ModuleDefinition definition;
public final JsonObject parameters;

private MetaModule(Identifier id, Resource resource)
throws IllegalStateException
{
this.id = id;
var dataResult = DefinitionFromResource(resource);
if (dataResult.isError()){
throw new IllegalStateException(dataResult.error().get().message());
}
var pair = dataResult.getOrThrow();
this.definition = pair.first;
this.parameters = pair.second;

try {
this.manager = ModuleRegistry.CreateManager(this.definition, this.parameters);
}
catch (IllegalStateException e){
throw new IllegalStateException(e);
}
}
}

@Override
public CompletableFuture<Map<Item,VariantManager>> load(ResourceManager resourceManager, Executor executor){
public CompletableFuture<ModuleLoader.Result> load(ResourceManager resourceManager, Executor executor){
return CompletableFuture.supplyAsync(()->ReloadModules(resourceManager), executor);
}

private Map<Item,VariantManager> ReloadModules(ResourceManager manager){
Map<Item,VariantManager> result = new HashMap<>();
static private ModuleLoader.Result ReloadModules(ResourceManager manager)
{
ModuleLoader.Result result = new ModuleLoader.Result();

for (Map.Entry<Identifier, Resource> entry : manager.findResources("variant-cits/item", id->id.getPath().endsWith(".json")).entrySet())
{
Identifier resourceId = entry.getKey();
Item item = ItemFromResourcePath(resourceId);
if (item == null)
MetaModule module;
try {
module = new MetaModule(entry.getKey(), entry.getValue());
}
catch (IllegalStateException e){
VariantsCitMod.LOGGER.error("Error in cit module {}: {}", resourceId, e);
continue;

DataResult<VariantManager> dataResult = ManagerFromResource(entry.getValue());
if (dataResult.isSuccess()){
VariantManager module = dataResult.getOrThrow();
module.ReloadVariants(manager);
result.put(item, module);
}
else {
VariantsCitMod.LOGGER.error("Error in cit module {}: {}", resourceId, dataResult.error().get().message());

module.manager.ReloadVariants(manager);

Set<RegistryEntry<Item>> targets = module.definition.targets()
.map(ModuleLoader::ItemsFromTarget)
.orElseGet(()->ItemsFromResourcePath(resourceId))
;

for (var item : targets){
result.modulesPerItem.computeIfAbsent(item, __->new ArrayList<>()).add(module);
}
}

// Sort highest priorities first.
for (List<MetaModule> modules : result.modulesPerItem.values()){
modules.sort((a,b) -> -Integer.compare(a.definition.priority(), b.definition.priority()));
}
return result;
}

static private Set<RegistryEntry<Item>> ItemsFromTarget(List<Identifier> targets){
Set<RegistryEntry<Item>> result = new HashSet<>();
targets.stream()
.map(id->Registries.ITEM.getEntry(id).get())
.filter(o->o!=null)
.forEach(result::add)
;
return result;
}

static private @Nullable Item ItemFromResourcePath(Identifier resourceId){
static private Set<RegistryEntry<Item>> ItemsFromResourcePath(Identifier resourceId){
String path = resourceId.getPath();
path = path.substring("variant-cits/item".length() + 1, path.length() - ".json".length());

Identifier itemId = Identifier.of(resourceId.getNamespace(), path);

if (Registries.ITEM.containsId(itemId))
return Registries.ITEM.get(itemId);
return Set.of(Registries.ITEM.getEntry(itemId).get());
else
return null;
return Set.of();
}

static private DataResult<VariantManager> ManagerFromResource(Resource resource){
static private DataResult<Pair<ModuleDefinition, JsonObject>> DefinitionFromResource(Resource resource){
JsonObject json;
try {
json = JsonHelper.deserialize(resource.getReader());
Expand All @@ -81,9 +144,9 @@ static private DataResult<VariantManager> ManagerFromResource(Resource resource)
if (parameters == null)
parameters = new JsonObject();

return DataResult.success(ModuleRegistry.CreateManager(definition, parameters));
return DataResult.success(Pair.of(definition, parameters));
}
catch (IllegalArgumentException|IllegalStateException|ClassCastException e){
catch (IllegalStateException|ClassCastException e){
return DataResult.error(e::toString);
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/fr/estecka/variantscit/ModuleRegistry.java
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,13 @@ static private void RegisterManager(Identifier type, ManagerFactory factory){
}

static public VariantManager CreateManager(ModuleDefinition definition, JsonObject customData)
throws IllegalArgumentException, IllegalStateException
throws IllegalStateException
{
assert definition != null;
Identifier type = definition.type();

if (!MODULE_TYPES.containsKey(type))
throw new IllegalArgumentException("Unknown module type: " + type.toString());
throw new IllegalStateException("Unknown module type: " + type.toString());

return MODULE_TYPES.get(type).build(definition, customData);
}
Expand Down
7 changes: 6 additions & 1 deletion src/main/java/fr/estecka/variantscit/VariantManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import net.minecraft.util.Identifier;

public final class VariantManager
implements IVariantManager
implements IVariantManager, IItemModelProvider
{
private final ICitModule module;
private final String prefix;
Expand All @@ -40,6 +40,11 @@ public VariantManager(ModuleDefinition definition, ICitModule module){
return GetVariantModel(module.GetItemVariant(stack));
}

@Override
public boolean HasVariantModel(Identifier variant){
return this.variantModels.containsKey(variant);
}

@Override
public @Nullable ModelIdentifier GetVariantModel(Identifier variant){
if (variant == null)
Expand Down
Loading

0 comments on commit 1d62e2d

Please sign in to comment.