ItemAdditions: Dynamic Appearances

An item addition with dynamic appearances, and what you can do for this

Summary

Published: 29 Oct. 2023 by manavortex Last documented update: Jul 05 2024 by manavortex

This guide will cover a sub-case of Adding new itemsvia ArchiveXL (added in 1.13). Dynamic variants are both easier and more flexible. Unless you don't need different appearances, you will want to default to this approach.

Wait, this is not what I want!

  • If you are an absolute beginner who has never done this before, check out Adding new items. The guide will tell you when to switch over.

  • You can find the technical documentation for dynamic variants on ArchiveXL's github.

  • If you want to create an Atelier store, see Your own Atelier Store

  • To quickly generate instances with up to two keys, check out W's generator (Codepen.IO)

Prerequisites

You need at least the following tools and versions (get the most recent):

TL;DR

In your root_entity:

  1. Add the DynamicAppearance tag

  2. Delete all but one entry from the appearances array.

    1. Name it like your entityName in the yaml

    2. Leave the appearanceName blank

In your .app:

  1. Delete all but one entry from the appearances array

  2. Name it like your entityName in the yaml

  3. Delete all components

  4. Point it to your mesh_entity

In your mesh_entity:

  1. To enable substitution in mesh depot paths, make sure they start with *

  2. Put all attributes that ArchiveXL should switch out in the paths in {}

e.g

meshes/t2_pwa_base_body_jacket.mesh meshes/t2_pwa_ebbwtfbbq_jacket.mesh

after:

*meshes/t2_pwa_{body}_jacket.mesh

You define an appearance as dynamic by adding the DynamicAppearance tag to the visual tags in its root entity.

If you don't know what that means yet, read on โ€” it will hopefully become clear soon.

How is this better than the old approach?

TL;DR: It just is, source: trust me bro. Proceed to the next section.

With vanilla item additions, you need one entry in the root entity per suffix. This gets out of hand quickly. When making stockings, there are four feet states (flat, flat_shoes, lifted and high_heels), and two body genders (Male and Female) . That leads to 4x2 entries for a single item.

For 15 appearances (colour variants) per mesh, I (manavortex) ended up with

  • 120 entries in the root entity (4*15 per body gender)

  • 120 entries in the .app file, (4*15 per body gender)

  • six mesh_entity files (flat, lifted, and heels for each body gender. I used the same for flat and flat_shoes, or I'd have ended up with eight.)

The most frustrating part was that everything was just duplication. Each set of entries in the .app file would only differ by name (_pwa and _pma to select it from the root entity), and the mesh entity path in partValues. Everything else was virtually identical, but I had to copy-paste and maintain 120 entries.

I cried to psiberx, who went and made the problem go away.

Dynamic variants put the logic into the mesh entity file. Instead of defining appearances with suffixes, I can conditionally define which component gets loaded, and ArchiveXL does the rest.

vanilladynamic

number of root_entity entries

120

1

number of .app entries

120

1

number of mesh entity files

6

1

number of components per mesh entity

2

5

If you still aren't convinced, go to Adding new items and start duplicating entries. Everyone else, to the batmobile!

Skipping and skimming

This guide contains the minimal amount of fluff and will link background information rather than giving it. Any links will tell you what you're supposed to read.

For that reason, you shouldn't skip or skim unless the section tells you that it's optional.

That being said, make sure to check the section

Step 0: Download the example project

This guide assumes that you have access to the prepared example project, so go and grab it.

  1. Find the template project on Nexus.

  2. Download it and extract the files to your project's root folder, so that the source directory merges with the existing one.

Step 1: Understanding the process

You will be able to change the template project just by following the steps, but if you want to make your own mods, then you're going to want to understand this.

Depending on how you learn best, you can also fuck around and try to understand the connections yourself. In that case, check Exercise 1: Create more records at the end of the section.

The yaml

For a general explanation of what the yaml file does, check The control file: yourModName.yaml. This section will only cover the differences between a dynamic and a regular yaml.

This file contains the biggest part of the dynamic magic.

This is your dynamic project's yaml file, minus any properties that aren't influenced by the dynamic appearances:

Items.my_custom_shirt_dynamic_$(base_color)_$(ribbons):
  $base: Items.GenericInnerChestClothing
  $instances:
    - { base_color: white, ribbons: red,  icon: 01 }
    - { base_color: black, ribbons: red,  icon: 02 }
    - { base_color: black, ribbons: blue, icon: 03 }
  appearanceName: root_entity_dynamic_appearance_!$(base_color)+$(ribbons)
  displayName: my_custom_shirt_dynamic_i18n_$(base_color)_$(ribbons)
  icon:
    atlasResourcePath: tutorial\torso\my_custom_shirt_dynamic_variants\ops\preview_icons.inkatlas
    atlasPartName: slot_$(icon)

This section will explain how that works - except for the appearanceName, you will find that in The root_entity.

Record names

TweakXL will generate one record per entry in $instances, according to the rules that you're using in the item name. This happens via property interpolation.

The example above will generate three item entries by substituting $(property_name) with the value of the property from the entry. If that isn't clear enough, check the example and the resulting item codes at the end of the line.

tems.my_custom_shirt_dynamic_$(base_color)_$(ribbons):
  $instances:
    - { base_color: white, ribbons: red,  icon: 01 } # Items.my_custom_shirt_dynamic_white_red
    - { base_color: black, ribbons: red,  icon: 02 } # Items.my_custom_shirt_dynamic_black_red
    - { base_color: black, ribbons: blue, icon: 03 } # Items.my_custom_shirt_dynamic_black_blue
Game.AddToInventory("Items.my_custom_shirt_dynamic_white_red") 
Game.AddToInventory("Items.my_custom_shirt_dynamic_black_red") 
Game.AddToInventory("Items.my_custom_shirt_dynamic_black_blue") 

Display names

Like the record names, the displayName property is also generated for each entry:

  $instances:
    - { base_color: white, ribbons: red,  icon: 01 } # my_custom_shirt_dynamic_i18n_white_red
    - { base_color: black, ribbons: red,  icon: 02 } # my_custom_shirt_dynamic_i18n_black_red
    - { base_color: black, ribbons: blue, icon: 03 } # my_custom_shirt_dynamic_i18n_black_blue

All you need to do is to make sure that such an entry exists in your localization file.

Icons

The icon name in the record is also generated for each entry. They are all using the same inkatlas, but you can generate that as well if you want - I've done it for the Netrunner suits, since I needed more than 100 icons.

  $instances:
    - { base_color: white, ribbons: red,  icon: 01 } # slot_01
    - { base_color: black, ribbons: red,  icon: 02 } # slot_02
    - { base_color: black, ribbons: blue, icon: 03 } # slot_03

The only important thing here is that the naming follows your inkatlas file's slot definitions.

Exercise 1: Create more records

With clever hook-ups in the mesh entity, you can set up your items so that they can be changed with a simple .yaml edit โ€” that means, the user can switch out the ribbon colour without ever starting Wolvenkit!

We'll use that here to enable "hidden" appearances.

I have hooked up the example project to support two base colours:

  • black

  • white

and three ribbon colors:

  • red

  • blue

  • green

By editing the $instances block in the .yaml, you should be able to spawn 6 different shirt in the game without touching any of the additional files!

The root_entity

For a general explanation of the root entity, check root_entity.ent. This section will only cover the differences between a dynamic and a regular root entity.

This is where you enable the feature by adding the tag DynamicAppearance to the visualTagsSchema (the last entry in the file):

For dynamic appearances, your root_entity file will contain one entry. Each item should have its own root entity.

The appearance name in the root entity corresponds to the appearanceName property in the .yaml without the variant:

You can leave the appearanceName blank. In that case, ArchiveXL will look for an appearance with the same name as the name attribute.

Since the appearance in the .app is called app_file_dynamic_appearance for clarity, and there is no root_entity_dynamic_appearance_ in the .app, this will not work for the example project.

The .app

For a general explanation of the .app file, check appearance.app. This section will only cover the differences between a dynamic and a regular .app file.

For dynamic variants, components in the .app file will be ignored. You have to use a mesh entity.

.app file: Conditional switching

In the context of our example project, this means that you can define your appearances like this:

Appearance nameExplanation

app_file_dynamic_appearance

Your regular appearance. Is displayed when none of the conditional ones apply.

app_file_dynamic_appearance&camera=tpp

This is only active in third person perspective. The item will be completely invisible in first person.

app_file_dynamic_appearance&camera=fpp

This becomes active whenever you are in first person perspective. You'll usually want this to remove the mesh from your face via partsOverrides.

app_file_dynamic_appearance&gender=male

You shouldn't do this โ€” instead, use Substitutions in the mesh file path.

The mesh_entity

For a general explanation of the mesh entity, check mesh_entity.ent. This section will only cover the differences between a dynamic and a regular mesh entity.

Unless you are using .app file: Conditional switching, this is where the magic happens.

Like appearance definition names, components in the mesh entity support .ent file: conditional switching. On top of that, they also support Substitutions.

Check The diagram's bottom left corner for a demonstration of both, or read up the ArchiveXL documentation on how they work.

Substitutions

In your mesh entity, you can use substitutions in path names to load different meshes. This is better than .ent file: conditional switching because it won't create extra components.

To enable substitution, your depot path must begin with an asterisk *. Each substitution needs to be enclosed in braces, e.g. {gender}.

.ent file: conditional switching

ArchiveXL will create all components, hiding those that aren't matched by your current conditions. If possible, use Substitutions instead.

Just like in the .app file, you can apply conditional switching to component names. It works exactly like .app file: Conditional switching:

The diagram

Now let's look at what we just did and check the diagram. You'll see that the control files are almost identical to the vanilla variants, but that the rest of the files has gotten a lot more manageable:

And that's it! With this and the original guide, you should hopefully be able to add items to your heart's content!

Tools and utilities

Generating display names

I have written a Python script to auto-generate display names, you can find it on my github. If you don't know how to use this, check Running Python Scripts.

Troubleshooting

Last updated