Shipping layouts
The two ways to hand BusinessBridge your BusinessLayoutSet JSON — in memory from
your AssetBundle, or as files on disk — and the asset-naming rule that trips people up.
A layout is one BusinessLayoutSet in the game's own JSON format: the fixtures, shelves
and products an AI store of your business type is built from. You supply one per building
size/version you want the AI to be able to use. Which sizes those are is your
decision; BusinessBridge imposes no list and never substitutes one size for another.
Two transports, one validator
| Field | Use it when |
|---|---|
LayoutContents |
Your layouts ship inside your AssetBundle, which is the normal case. Contract stage
beta-2 and later. |
LayoutFiles |
Your layouts really are loose .json files on disk — a development mod, or
content you write at runtime. Supported since beta-1 and still supported. |
Set either, or both: they become one list as soon as BusinessBridge has them, and from that point on
the same deserializer, the same rules and the same messages apply. A layout cannot pass validation
because of how it arrived. Two layouts claiming the same BuildingSize and
BuildingVersion are rejected whichever list they came from.
The shape of LayoutContents
public List<AiBusinessLayout> LayoutContents = new List<AiBusinessLayout>();
public sealed class AiBusinessLayout
{
public string Name;
public string Json;
}
Json is the text of the layout file. Name is the logical layout
name — it stands in for what the file name would have been, so it must equal the
registration's LayoutName. It becomes the layout's name, overriding whatever
LayoutName the JSON itself carries, and it labels the layout in validation messages.
The field is a string rather than a TextAsset on purpose: BusinessBridge
never holds a reference to an asset whose bundle you may unload, and you stay free to load the text
however you like.
Give every layout asset a unique file name
One LayoutName covers all of your building sizes. So the obvious layout on
disk — YourShopImporters.json in a folder per size — puts several
identically named assets into one AssetBundle. Unity generates deterministic ids from asset
names, and duplicates fail the build with a hash collision that can still leave a
valid-looking but empty bundle behind.
Put the size — and, if one bundle carries several business types, the type — in the
file name, and pass the logical layout name explicitly instead of asset.name.
Bundling and loading several sizes
A .json file in your mod folder is imported as a TextAsset, so it travels
in your bundle with everything else. Name the files so they are unique:
Assets/Mods/YourMod/Layouts/C1/yourshop_C1_layout.json
Assets/Mods/YourMod/Layouts/C2/yourshop_C2_layout.json
Assets/Mods/YourMod/Layouts/D2/yourshop_D2_layout.json
Then load them and assign the logical name yourself:
const string LayoutName = "YourShopImporters"; // the logical AI layout name
var sizes = new[] { "C1", "C2", "D2" };
var layouts = new List<AiBusinessLayout>();
var bundle = AssetService.GetBundle(context.ModId, YourBundleKey);
foreach (var size in sizes)
{
var asset = bundle.LoadAsset<TextAsset>($"Assets/Mods/YourMod/Layouts/{size}/yourshop_{size}_layout.json");
if (asset == null || string.IsNullOrEmpty(asset.text)) continue; // log it; do not guess a substitute
layouts.Add(new AiBusinessLayout(LayoutName, asset.text)); // LayoutName, NOT asset.name
}
Two different names
The asset file name only has to locate the asset in your bundle, and must be
unique within it. The logical layout name (AiBusinessLayout.Name and
the registration's LayoutName) is what the game matches
AiBusinessDefault.buildingLayout against, and is shared by all your sizes. They are
separate concepts and do not have to match.
What tells your sizes apart is BuildingSize and BuildingVersion
inside each JSON document — never the file name.
If you use LayoutFiles instead
There BusinessBridge takes the layout's name from the path, exactly as the game does, so each file
must be named <LayoutName>.json — one per size, in separate folders.
This is the beta-1 behaviour and it has not changed.
LayoutFiles = { c1Path, c2Path }, // each file named YourShopImporters.json
What is checked
Per layout, whichever transport it arrived on:
- the text parses as a
BusinessLayoutSet - its
BusinessTypeis the business type you are registering - its layout name equals the registration's
LayoutName BuildingSizeis non-empty,BuildingVersionis 1 or more, and no two layouts claim the same size and version- every item, and every product a shelf sells, is a real item in this build
- any primary product of your business type is a demanded product
- at least one shelf actually sells something
Any of those failing rejects the whole registration: nothing is stored and nothing is injected. A layout for a building size the city has no building of is reported as a note instead — it is valid, the AI simply never gets to use it. See Registering a business type for the full list and the messages.