Rain World resolves a requested relative path against generated merge output, enabled mods, and base StreamingAssets. The caller still has to request that path. Placing a file in a directory does not make every subsystem discover it.

flowchart TD
    A[Relative asset path] --> B{mergedmods file exists}
    B -->|yes| C[Use merged file]
    B -->|no| D[Check active mods]
    D --> E[Console and base paths]

First-match file resolution

global::AssetManager.ResolveFilePath(System.String,System.Boolean,System.Boolean) normalizes directory separators and lowercases the relative path used for each check. With the default flags, build 22785462 searches in this order:

PriorityLocation
1StreamingAssets/mergedmods/<path>
2Active mods from the last entry to the first, each checking v1.11.8/<path>, then newest/<path>, then <path> at the mod root
3Applicable console content
4StreamingAssets/<path>

The final return value is the base path even when that file does not exist. Callers that require a real file still need an existence check.

global::AssetManager.ResolveDirectory(System.String) uses the same first-match direction for mod directories. Version folders are exposed by global::ModManager.Mod.TargetedPath and global::ModManager.Mod.NewestPath. For this baseline, TargetedPath is the literal v1.11.8 directory. Put shared files at the mod root and use that version directory only for files intended for this game version.

ResolveAllFilePaths has a different mod order

global::AssetManager.ResolveAllFilePaths(System.String) collects every existing match. It adds mergedmods first, then iterates ActiveMods from the first entry to the last. Within each mod it adds the targeted version, newest, and root matches. Console and base files follow.

This forward active-mod iteration differs from the reverse iteration used by ResolveFilePath. Code that consumes all returned files must define how it handles duplicates. Do not infer first-match precedence from the list order.

Normal files and modify inputs

A normal content file is a candidate for direct resolution. A file under a mod’s modify/ tree is an input to the merger. global::ModManager.GenerateMergedMods(global::ModManager.ModApplyer,System.Collections.Generic.List<System.Boolean>) rebuilds generated files after enabled-mod changes. Keep authored source under the mod root and treat mergedmods as disposable output.

global::ModManager.ModMerger.PendingApply.ApplyMerges(global::ModManager.Mod,global::ModManager.ModMerger,System.String) chooses merge behavior from the destination filename:

DestinationMerge behavior
properties.txt, Sounds.txt, locks.txtReplace or combine entries by parsed key
regions.txt, mpmusic.txt, egates.txtUnion complete lines through a HashSet
world_*.txtParse and merge world sections, room links, and spawns
Room settings and templatesReplace scalar keys and union selected collections
Most other filesWrite the supplied merge lines as the resulting file

global::ModManager.ModMerger.MergeRoomSettings(global::ModManager.Mod,System.String,System.String[]) unions Effects, PlacedObjects, AmbientSounds, and Triggers through a HashSet. Its output order is unspecified. FadePalette keeps the entry with at least as many comma-separated fields. Other ordinary keys take the incoming value. Identify records by their content and avoid behavior that depends on collection order.

World file merging deserves a full traversal test. global::ModManager.ModMerger.MergeWorldFiles(global::ModManager.Mod,System.String,System.String[]) adds new rooms, combines matching connections while accounting for DISCONNECTED slots, and merges spawn records separately. Test each changed exit and spawn after merge regeneration.

Caller-owned image paths

The baseline has no generic loader that registers every PNG found in a mod’s assets/ directory. Mod code may own a unique relative path:

string path = global::AssetManager.ResolveFilePath(
    "assets/harbor_marker.png",
    false,
    false);

That lookup requests a complete filename and only returns a path. Check that the file exists. Futile’s loading arguments are a separate concern and depend on Futile.resourceSuffix, atlas data, and unique element names. Do not pass this full PNG filename unchanged to an API that appends its own extension. Atlases and shaders covers the registry and duplicate-name behavior.

No running game or two-mod priority test was used for this article. The search loops, version paths, and merge algorithms were verified from the build 22785462 source.

Sources

  • Rain World v1.11.8, Steam build 22785462: global::AssetManager.ResolveFilePath(System.String,System.Boolean,System.Boolean), global::AssetManager.ResolveAllFilePaths(System.String), and global::AssetManager.ResolveDirectory(System.String) in the assembly identified by Runtime reference.
  • Rain World v1.11.8, Steam build 22785462: global::ModManager.Mod.TargetedPath, global::ModManager.Mod.NewestPath, global::ModManager.GenerateMergedMods(global::ModManager.ModApplyer,System.Collections.Generic.List<System.Boolean>), and the named global::ModManager.ModMerger methods.