User Tools

Site Tools


tutorial:custom_resources

Custom Data/Resource pack Resources

A Foreword

While not necessary to any extent, it is suggested that you first get accustomed to the basics of Fabric modding and Json parsing before diving into this. Going through the process of adding a recipe type is a good way to get a handle on Json.

Furthermore, most examples in this list will be dealing with data packs. However, the process for processing resource packs is effectively the same.

Reload Listeners

Data packs and resource packs are wonderful things, they are the heart of data-driven design in the Minecraft modding scene, and as such, learning to wrangle them is an important step of one's growth as a modder.

Vanilla comes with a variety of data and resource pack resources, commonly known are things like textures, models, and after the havoc that was wrecked in 1.16.2, biomes, but this can be extended to be able to load custom data from data and resource packs. The process of extending the resource system to suit your mod's needs will be the main topic of this article, and it all starts with the use of ResourceManagerHelper.

Any and all work you plan to do regarding resources must be registered through ResourceManagerHelper, or more specifically, one of its resource types through ResourceManagerHelper#get(ResourceType), where the type can be either SERVER_DATA or CLIENT_RESOURCES, corresponding to data pack and resource pack resources respectively, or in a dev env, the files found within the resources/assets and resources/data folders respectively (in case you are wondering, yes, your mod's assets and data are handled in the same way as resource packs and data packs internally, so this will also handle anything put in there).

From here on forward, examples will be centered around data packs and use ResourceType.SERVER_DATA were applicable, do remember that the process for data and resource pack resource processing is the same except for what ResourceType is being called.

Continuing on the registry side of things, registering your reload listener is done through ResourceManagerHelper#get(ResourceType).registerReloadListener(IdentifiableResourceReloadListener). Notably once registered your listener will not only handle the initial load of resources, but any subsequent reloads such as those done by /reload and F3 + T

  1. public class ExampleMod implements ModInitializer {
  2. @Override
  3. public void onInitialize() {
  4. ResourceManagerHelper.get(ResourceType.SERVER_DATA).registerReloadListener([...]);
  5. }
  6.  
  7. [...]
  8. }

Reload Listeners 2: The Listener

We have now gone over the process of registering your reload listeners, but you have yet to actually write one, that part of the process will be discussed in this section (specifically, this section will discuss SimpleSynchronousResourceReloadListener, async listeners will be discussed later).

To simplify this tutorial, an anonymous class is used to implement SimpleSynchronousResourceReloadListener, so that all the code is in one file.

  1. new SimpleSynchronousResourceReloadListener() {
  2. @Override
  3. public Identifier getFabricId() {
  4. return new Identifier("tutorial", "my_resources");
  5. }
  6.  
  7. @Override
  8. public void reload(ResourceManager manager) {
  9. [...]
  10. }
  11. }

What you are seeing here is the actual resource reload listener (technically a resource reload listener, this is not the only type, however, it is the easiest to implement) that will be registered through ResourceManagerHelper. As you can see there are two notable parts to it, the getFabricId and reload methods. The getFabricId method is the simpler of the two, you just need to return a unique identifier for the reload listener in question, nothing fancy. The reload method, however, is the main area of interest.

The reload method supplies you with a ResourceManager with which to load whatever data you want within the constraints of the registry's resource type (remember, SERVER_DATA can only access data packs, CLIENT_RESOURCES can only access resource packs). What you do with the resource manager is really mostly up to you, however, if you are making some aspect of your mod data-driven then you probably want to read from a specific folder. Doing this is simple but it does require a bit of care.

Firstly, you are going to want to clear or otherwise prepare to update anything that is storing the info you are going to be fetching through the manager, otherwise, your mod will likely break whenever /reload or F3 + T is used. Once that is done you should then use ResourceManager#findResources(String path, Predicate<String> pathPredicate) to fetch the files contained within whatever folder you want (note: the path is implicitly rooted in either the data or assets folder of all processed data/resource packs). This will get you a collection with the paths to all files that are within the specified path and meet the predicate's criteria (you are probably want to filter for a specific file type, .json in this tutorial), you will then need to iterate through the collection and do stuff with those paths.

  1. @Override
  2. public void reload(ResourceManager manager) {
  3. // Clear caches here
  4.  
  5. for(Identifier id : manager.findResources("my_resource_folder", path -> path.endsWith(".json"))) {
  6. try(InputStream stream = manager.getResource(id).getInputStream()) {
  7. // Consume the stream however you want, medium, rare, or well done.
  8. } catch(Exception e) {
  9. TUTORIAL_LOG.error("Error occurred while loading resource json " + id.toString(), e);
  10. }
  11.  
  12. }
  13. [...]
  14. }

There are two important things to note here. Firstly, all resources must be processed as InputStreams. This means that you need to close the resources after processing. Forgetting to do this is an easy way to end up with a resource leak. The easiest way to make sure any and all processed resources get closed is to process them using a try-with-resources block as shown above. The second thing to note is that there is no way to obtain the raw file you are processing, so the “must” part of “all files must be processed as InputStreams” is an actual must and not a should. Conveniently, there are many parsers that will get you your desired file from an input stream, particularly for Json.

Once you are done, your code should look somewhat like this. At this point, you can pat yourself in the back as you have successfully made a part of your mod data-driven! Any files contained in the “my_resource_folder” folder of data packs or mod data will get picked up by the this.

  1. public class ExampleMod implements ModInitializer {
  2. @Override
  3. public void onInitialize() {
  4. ResourceManagerHelper.get(ResourceType.SERVER_DATA).registerReloadListener(new SimpleSynchronousResourceReloadListener() {
  5. @Override
  6. public Identifier getFabricId() {
  7. return new Identifier("tutorial", "my_resources");
  8. }
  9.  
  10. @Override
  11. public void reload(ResourceManager manager) {
  12. // Clear Caches Here
  13.  
  14. for(Identifier id : manager.findResources("my_resource_folder", path -> path.endsWith(".json"))) {
  15. try(InputStream stream = manager.getResource(id).getInputStream()) {
  16. // Consume the stream however you want, medium, rare, or well done.
  17. } catch(Exception e) {
  18. TUTORIAL_LOG.error("Error occurred while loading resource json" + id.toString(), e);
  19. }
  20. }
  21. }
  22. });
  23. }
  24. [...]
  25. }
tutorial/custom_resources.txt · Last modified: 2024/06/29 07:10 by daomephsta