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:
| Priority | Location |
|---|---|
| 1 | StreamingAssets/mergedmods/<path> |
| 2 | Active mods from the last entry to the first, each checking v1.11.8/<path>, then newest/<path>, then <path> at the mod root |
| 3 | Applicable console content |
| 4 | StreamingAssets/<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:
| Destination | Merge behavior |
|---|---|
properties.txt, Sounds.txt, locks.txt | Replace or combine entries by parsed key |
regions.txt, mpmusic.txt, egates.txt | Union complete lines through a HashSet |
world_*.txt | Parse and merge world sections, room links, and spawns |
| Room settings and templates | Replace scalar keys and union selected collections |
| Most other files | Write 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 build22785462:global::AssetManager.ResolveFilePath(System.String,System.Boolean,System.Boolean),global::AssetManager.ResolveAllFilePaths(System.String), andglobal::AssetManager.ResolveDirectory(System.String)in the assembly identified by Runtime reference. - Rain World
v1.11.8, Steam build22785462:global::ModManager.Mod.TargetedPath,global::ModManager.Mod.NewestPath,global::ModManager.GenerateMergedMods(global::ModManager.ModApplyer,System.Collections.Generic.List<System.Boolean>), and the namedglobal::ModManager.ModMergermethods.