Attachments system description

Attachments on horses are handled by a bunch of different system spread across multiple modules and classes. The system currently allows you to associate an HorseMod.AttachmentSlot to an attachment point on the horse model and define slots various items can occupy with different caracteristics defined as HorseMod.AttachmentDefinition.

AttachmentData

HorseMod.attachments.AttachmentData is the main module used to store all the attachment slots and definitions. Attachments of a specific horse are stored in a HorseMod.AttachmentsModData instance associated to the horse using the HorseMod.HorseModData system. Containers information are stored in HorseMod.ContainersModData instances and manes in HorseMod.ManesModData instances.

The following tables are generated at runtime from HorseMod.attachments.AttachmentData.slotsDefinitions:

  • HorseMod.attachments.AttachmentData.slots

  • HorseMod.attachments.AttachmentData.maneSlots

  • HorseMod.attachments.AttachmentData.containerItems

It will verify that the provided attachments are of the right format and log errors if not:

  • Checks that every slot definition has a HorseMod.SlotDefinition.modelAttachment point defined.

  • Creates the apparel location in the Animal attached locations group for every slot defined.

  • If an attachment definition has a HorseMod.ContainerBehavior, it checks that the item actually is a container (ItemType.CONTAINER). It also verifies that the HorseMod.ContainerBehavior.worldItem invisible container has the same capacity as the accessory item.

  • Removes any HorseMod.ItemDefinition entry from HorseMod.attachments.AttachmentData.items that don’t contain any attachment definitions.

Mane management

Manes are a special type of attachment that can be colored and are made of multiple parts attached to different points on the horse model. The mane system is handled by the HorseMod.attachments.ManeManager module, which provides functions to setup and remove manes on horse models. When a horse spawns, its manes configuration and color are automatically setup based on its breed using HorseMod.attachments.AttachmentData.maneByBreed.

In the future, we plan to expand this system by allowing players to customize their horse’s mane color and style through in-game actions or items.

Attachment visuals

The visuals of an attachment are created by the HorseMod.attachments.AttachmentVisuals module. When an attachment is equipped, an InventoryItem is created from the full type of the attachment and equipped to the matching attachment slot. Note that the item is not persistent, is only used for its model, and does not exist on the server.

In general, adding/removing attachments should handle the visuals for you. You should only need to change them manually in special cases, for example reins animations are implemented by changing the visuals for the reins without changing the actual attached item. Do not expect any changes to be persistent: visuals are lost any time the animal goes offscreen, and recreated from their attachments when they go onscreen, as detailed below.

Attachment reapplying

Attachment visuals need to be reapplied whenever a horse model becomes visible again, which can be checked with IsoAnimal:getModel() (nil means not visible). This is handled by the HorseMod.attachments.AttachmentUpdater module, which is called every in-game tick but updates only a handful of horses per tick to spread the load over multiple ticks.

It detects whenever there is a change of visibility for horses and only triggers the reapplying process when a horse becomes visible again by using HorseMod.attachments.AttachmentUpdater.reapplyFor. This will simply iterate every attachments stored in the HorseMod.AttachmentsModData of the horse and attach back the InventoryItem if it can be found, or creates a fresh one if its reference can’t be found, which is usually the case whenever a horse was reloaded and not when it switches between visible and non visible.

This reapply then attaches again the attachment to the horse model using HorseMod.attachments.Attachments.setAttachedItem. If it is a mane, if first setup this mane with the use of HorseMod.attachments.ManeManager.setupMane.

Container managing

Containers attached on horses need to be accessible by the player while attached to the horse, but the attachment system of the base game loses the item references when those get attached on the horse and as such the containers get deleted and lost. To work around that, the attachment system uses invisible containers spawned in the world which follow the horse around and hold the items instead.

Whenever HorseMod.HorseEquipGear is used by the player and the attachment has a HorseMod.ContainerBehavior, HorseMod.attachments.ContainerManager.initContainer is called to create the invisible world container and transfer all the content to it. This HorseMod.ContainerInformation entry is stored in the horse HorseMod.ContainersModData to track which horse and slot it is associated with.

When the player unequips the attachment, HorseMod.attachments.ContainerManager.removeContainer is called to transfer back all the items from the invisible world container to the accessory item and delete the world container.

When the horse is unloaded from the area or the player logs out while having a horse with attachments, the invisible world containers references are removed from the horse attachments so whenever that horse is loaded back, it is forced to find back its containers.

When it comes to tracking the containers, HorseMod.attachments.ContainerManager.track is called for every horses from the HorseMod.attachments.AttachmentUpdater module. This function checks every containers on the horse.

If the container world item reference is found, and is on a different square than the horse, it removes this world item and uses its InventoryItem instance to create a new invisible world container on the new square. This is needed because we can’t directly move world items between squares.

If the container world item reference is missing, we need to find back the invisible world container. To do so, we search for different possibilities:

  1. Check the HorseMod.attachments.ContainerManager.ORPHAN_CONTAINERS cache table for the world item ID.

  1. If it is found, we verify that this container is in fact our container by checking its mod data. If it is our container, we remove it from the cache.

  2. If it isn’t our container (which it shouldn’t happen normally), we verify it is a horse container and update its item ID in the cache.

  3. If it isn’t a horse container, we remove it from the cache and continue searching.

  1. Use the stored world item XYZ coordinates to search for the container near that position.

  1. If a container is found, we verify it is our container by checking its ID.

  2. If it isn’t our container, we verify it is a horse container and cache it as orphaned.

  1. Because animals don’t seem move when unloaded, we search for the container at the position of the horse when loading the area.

  1. If a container is found, we verify it is our container by checking its ID.

  2. If it isn’t our container, we verify it is a horse container and cache it as orphaned.

Alternatively in the future if these checks aren’t enough, containers could be retrieved by checking squares of newly loaded chunks for the container attachments world items.

Whenever a container is found, its reference is reattached to the HorseMod.ContainerInformation of the horse attachment.