Intro[]
The modding API in Rise of Industry is able to load custom compiled C# code files in the form of dll files. To create the dll files we strongly recommend using Visual Studio. (the free Community edition will work just fine) and Unity 2018.1.0f2 (do not use Unity 2018.2). There are no current plans for adding code written in Lua.
To write code that the API can understand, you'll have to create a new C# library project and add the references you need. References can be found in the Rise of Industry_Data/Managed folder in the game's root folder (Downloaded via Steam or GoG).
If you just want to learn how to translate the game, click HERE.
Before Starting[]
The steps to create your own dll that Rise of Industry can correctly import are the following (here we assume you are using Visual Studio 2017 Community):
- Create a new Class Library project using the menu command File -> New -> Project… and then choose the Class Library project template
- Select “Class Library (.NET Framework)”, found under “Windows Classic Desktop”
- Now you will need to reference all the assemblies (which are just dll files themselves) that you need. In practice you will always need to at least reference the core Unity assemblies that give you access to classes like MonoBehaviour. To add a reference to an assembly you will need to:
- Right click your project in the Solution Explorer tab within Visual Studio
- In the context menu that opens select the command Add -> References…
- In the next window (Reference Manager) click the Browse… button at the bottom. There is also a Browse entry on the left side of the window, please ignore that
- Now you will need to navigate to the folder where Rise of Industry is installed, we will call it [RoI installation folder]
- Once you are there you will have to go to the [RoI installation folder]/Rise of Industry_Data/Managed subfolder
- In that folder you’ll find all the dll files that you need to add
- The ones that you should always add are:
- Assembly-CSharp.dll
- UnityEngine.dll
- UnityEngine.CoreModule.dll
- And other assemblies if you need them. Unfortunately we can’t possibly enumerate all classes and the assemblies where they can be found so you will need to refer to the Unity3D official documentation for that. For example if you need to access UGUI to create UI scripts you will need to add UnityEngine.UI.dll and UnityEngine.UIModule.dll
Documentation regarding the Unity engine can be found here while all of Rise of Industry's codebase documentation is here
Please note that our code lives in the namespace called ProjectAutomata and not something like RiseOfIndustry as one would expect. This is because the game suffered a name change mid-production but we kept using the old one internally.
The mod instance is created in a pre-init unity scene and is kept alive via DontDestroyOnLoad. It is also able to receive the regular Unity messages
EDIT: a new mod mega-guide was just added HERE. Have a look!
The Mod class[]
Now that we know how to create and set up a Visual Studio project for our code needs we will need to create our Mod class. The Mod class is the entry point of your mod. That means that the first line of code that your mod can execute has to be put there.
So the bare minimum you need to do is creating a new C# class and make it derive from our Mod class. This base class has methods you can override in order to run your own bootstrap logic. A basic example is:
using ProjectAutomata; // Our Mod class is here
using UnityEngine; // For Debug.Log
public class MyMod : Mod {
public override void OnAllModsLoaded() {
Debug.Log(“Hello Mod!”);
}
}
This mod will just log the string “Hello Mod!” to the console but you can use it as a starting point.
This mod class is actually a MonoBehaviour (because Mod is). When Rise of Industry is launched the first thing that happens is mods loading. When a mod is loaded a new GameObject is created and the Mod script you provided is attached to it. We use DontDestroyOnLoad on the GameObject so it will always be around and you can even implement standard Unity3D messages on it. For example you can add the following to the example class above:
//...
public class MyMod : Mod {
//...
private bool loggedAlready;
private void Update() {
if (!loggedAlready && Time.unscaledTime > 10) {
Debug.Log(“Ten seconds have elapsed!”);
loggedAlready = true;
}
}
}
Which will log the message “Ten seconds have elapsed!” after 10 seconds have elapsed since the game was launched.
Building Your DLL[]
Once you are happy with your code and you want to test it live you will need to build it into a dll file. Fortunately it is a very easy thing to do. In Visual Studio just use the command Build -> Build Solution and you’ll get your compiled dll ready to be used.
One (easy) way to find it is to right click on your project in the Solution Explorer tab and then use the command Open Folder in File Explorer. Unless you changed your project settings you will find your dll in the bin/Debug subfolder. The dll file you need is named after your mod, for example if your Class Library project is called MyMod then you should see a MyMod.dll file there.
Once you got the dll you need to put it into the right place inside [RoI installation folder]. This place is: [RoI installation folder]/Mods/MyMod/code (create the path if you need to).
Contents of a Mod[]
Mods should have a very specific file and folder structure when being created and uploaded.
In the root folder of the mod there must be:
- assets (Folder)
- code (Folder)
- content (Folder)
- desc.json
assets[]
All assets that should be loaded go here. Assets are anything that can be considered content (audio clips, models, textures, etc).
code[]
You reference our scripting assemblies in a Visual C# project and compile the project as class library (DLL).
Then, put the DLL in this folder so it can be loaded into our AppDomain.
content[]
All content that should be loaded goes here
desc.json[]
A description for the mod, for example:
{
"author":"Dapper Penguin Studios",
"name":"Example mod",
"version":1,
"versionString":"1.0.0",
"dependencies":["Example mod 3"]
}
Explanation of each variable:
- author: mod creator's name or username
- name: name of the mod
- version: integer used when checking newer / older versions of the same mod
- versionString: a string that is presented to the user of the mod
- dependencies: names of other mods that are required for the correct use of the current mod
Content Creation Models[]
Content creation models are data models which can be used to import data into the game. They provide an easy way of loading data by just providing the game a set of fields specified via JSon.
They are separated into 2 types:
- Models
- Component Models
Models can also be composite assets (GameObjects), those models can take additional arbitrary components.
Details[]
There are 2 types of content creation models - the ones for simple assets and the composite objects (GameObjects).
Simple assets follow this format:
{
"type": "TYPE",
"object":
{
MAINOBJECT
}
}
TYPE[]
The type of the object to be created.
This will automatically select a content creation model, alternatively the content creation model type can be directly specified here.
For example, "ProjectAutomata.ProductDefinition" will load the standard model for ProductDefinition, which is CCProductDefinitionModel.
"ProjectAutomata.CCProductDefinitionModel" will instead load the CCProductDefinitionModel without any lookups.
MAINOBJECT[]
This is the actual object that will be loaded.
The fields this object can accept can be looked up via the code reference.
When being used, you need to look up the model for the type (usually named CC[TYPE]Model, ex. CCProductDefinitionModel).
In the documentation all field names and what specifying them does is explained.
Composite assets follow this format:
{
"type": "{TYPE}",
"object":
{
MAINOBJECT
},
"components":
[
OBJECTS
]
}
OBJECTS[]
Json-Array with same format as simple assets, but the types loaded from here must be components.
Components are similarly handled to simple assets, their data structure is very much similar but their models are named CCC[TYPE]Model.
In unity they map to the MonoBehaviour components that can be added to GameObjects.
Examples[]
Simple Asset[]
{
"type":"ProjectAutomata.ProductDefinition",
"object":
{
"name":"TestProduct",
"displayName":"Test Product",
"description":"I was imported via JSON",
"category":"End Products"
}
}
This will create a new product in-game called "Test Product", which is considered an End Product, with the description "I was imported from JSON".
Composite asset[]
{
"type":"ProjectAutomata.ResourceNode",
"object":
{
"name":"TestNode",
"displayName":"Test node (Modded)",
"visualVariations": ["MinIron_01"],
"product": "Wood"
},
"components":
[
{
"type":"ProjectAutomata.SomeComponent",
"object":
{
"baseValue": 1;
}
}
]
}
This will create a new resource called "TestNode" that will give Wood when gathered, but will look like Iron, and worth exactly 1$.
Mod Uploader[]
How to use[]
First, download and extract all the 7z file anywhere on your hard drive.
In order to use the Mod Uploader you have to create a JSON file containing information about the mod to be uploaded.
Once this json file is created you just drag&drop it on "ProjectAutomata.WorkshopUploader.exe" and it will start uploading automagically.
Important: always use forward slash "/" instead of backwards "\" for the path.
After the upload is completed, the Uploader will overwrite your JSON file and add two fields, which create a mapping between the JSON and the actual steam workshop item.
Subsequent uploads will be treated as updates, so the mod can be updated the same way its uploaded, so keep them handy.
Example[]
{
"title": "Testmod2",
"description": "This is a test mod omgfz0r!",
"visibility": "PUBLIC",
"contentFolder": "E:/Unity/Projects/Project Automata Git/Mods/Trumpet/",
"mainPreview": "E:/Unity/Projects/Project Automata Git/Private/test.png",
"changeNote": "Added more preview stuff"
}
The fields should be pretty self-explanatory,
Changenote can be used as a changelog for the current version or like a git commit message.
Types of Custom Assets[]
The Rise of Industry Modding API allows you to load custom assets.
Assets will be located and loaded by their filename (without extension or, in the case of AssetBundles, by the asset name), they can be referenced in content creation for visualizations, audio, etc.
Several file formats are supported:
.bundle[]
Unity's AssetBundles
Loads all assets in the bundle and maps the types in the asset loaders for later referencing in content creation.
Recommended for large mods with lots of assets.
.svg[]
Vector / Illustrator type of image.
Maps to SVGAsset
.png[]
Normal texture that allows for transparency.
Maps to Texture2D
.sprite[]
JSon data structure that can create a sprite from a previously loaded texture
Maps to Sprite
Example:
{
"textureName": "test",
"rectPos": {"x": 0, "y": 0},
"rectSize": {"x": 10, "y": 10},
"pivot": {"x": 0.5, "y": 0.5}
}
This would grab the "test" texture (.png) and create a simple sprite in-game that is 10x10 px, with the pivot point in the middle
.sae[]
JSon data structure for simple audio event
Maps to SimpleAudioEvent
Example:
{
"clips": ["Clip1", "Clip2"],
"volume": {"min": 0.8, "max": 0.9},
"pitch": {"min": 0.9, "max": 1.1}
}
This would load Clip1 and Clip2, and every time they are played, their volume and pitch is randomised between the given values, for a more organic feel.
.cae[]
Json data structure for composite audio event
Maps to CompositeAudioEvent
Example:
{
"events": ["TestEvent1", "TestEvent2"]
}
Extending the Building Panel (Advanced)[]
Note: to modify anything relative to the UI, you need to have TextMesh Pro. The currently updated version is 1.2.4, found here.
To extend the building panel you have two choices:
- Create a new Building Panel Module (recommended)
- Create your own Building Panel! This is not recommended because it means you need to create everything from scratch, both UI and logic
Here we describe option number 1:
Our Building Panel is based on modules. A module is just one of the tabs that appear in the panel. In general you can add new modules via JSON and then you can attach logic scripts to them in your Mod derived class. Our Water Tower mod is a good example of how this can be done.
First the JSON:
{
"type": "ProjectAutomata.BuildingPanelModule",
"object":
{
"moduleName": "Water Distribution",
"uiOrder": 100,
"contentPrefab": "WaterTowerModule",
"typeRules":
[
{
"onlyPlayerOwned": "true",
"typeName": "WaterTower.WaterDistributor, WaterTower",
"typeInheritance": "true"
}
]
}
}
This is how the custom tab was added in our mod. The description of each property can be found in our class reference docs, specifically see the docs of the CCBuildingPanelModuleModel class.
The most interesting properties are “contentPrefab” and “typeRules”. The former is the name of a prefab loaded from an asset bundle. This prefab has no custom logic on it, it’s just made up of game objects with UGUI components on them. The “contentPrefab” property tells our Building Panel to use that prefab to instantiate the UI displayed in the custom “Water Distribution” (moduleName property) tab. The script that runs the panel logic is added to the prefab by our C# mod derived class (WaterTowerMod.cs).
The “typeRules” property is used here to specify for which buildings the tab should be displayed. The specific rule you see in the JSON above says to show the tab only for those buildings where the following conditions are met:
The building belongs to the player
The building prefab has a script of type “WaterTower.WaterDistributor, WaterTower” on it. Notice that the name after the comma is the name of your namespace which will usually be the same as the dll. You always have to specify it
The condition number 2 also applies if a script that derives from WaterDistributor is attached to the building prefab
Once you are done with the JSON file you will probably want to attach some logic to your custom tab. One possible way is how we did it in the Water Tower mod.
using ProjectAutomata;
namespace WaterTower {
public class WaterTowerMod : Mod {
// ...
public override void OnAllModsLoaded() {
// ...
var waterTowerModule = GameData.instance.GetAsset<BuildingPanelModule>("WaterTowerModule");
waterTowerModule.gameObject.AddComponent<WaterTowerUIController>();
}
}
}
The WaterTowerUIController script implements all the custom logic for the tab.
Mod Examples[]
Example 1: Acid Resource[]
You can download it here
Contents[]
- \Assets
- acid.svg (vectorial image for the new resource)
- acid.png (texture of the new resource)
- acid.sprite (final sprite for the resource)
- \Code
- (empty)
- \Content
- testProduct.json (code for the creation of new resource)
- testRecipe.json (code for the creation of new recipe)
- testResourceNode.json
- desc.json (description of the mod)
testProduct.json[]
{
"type":"ProjectAutomata.ProductDefinition",
"object":
{
"name":"testProduct",
"displayName":"Acid",
"description":"I was imported via JSON",
"category":"Raw Resource"
}
}
The creation of the new Acid resource. Purposefully bad practice to use vague terms like "testProduct". Call everything in a sensible manner, keeping the code nice and tidy.
testRecipe.json[]
{
"type":"ProjectAutomata.Recipe",
"object":
{
"name":"TestRecipe",
"displayName":"Chemicals",
"description":"I was also imported via JSON",
"gameDays": 10,
"excludeInRecipeGraph": false,
"allowWaterResources": false,
"requiredModules": [],
"requiredResourceNodeProducts": [],
"ingredients": [{"productName": "Acid", "amount": 3}],
"result": [{"productName": "Chemicals", "amount": 1}]
}
}
Allows the creation of a new recipe, which uses 3x Acid to make 1x Chemicals. Again, bad naming and missing description.
testResourceNode.json[]
{
"type":"ProjectAutomata.ResourceNode",
"object":
{
"name":"TestNode",
"displayName":"Acid (Modded)",
"visualVariations": ["acid"],
"product": "Acid"
}
}
Again with the incorrect naming! Anywho, this allows for our beloved Acid to be spawned in the world. Note that this would never work as there isn't an acid.assetbundle file in the mod folder or the game's data.
desc.json:[]
{
"author":"Dapper Penguin Studios",
"name":"Acid Resource",
"version":1,
"versionString":"1.0.0",
"dependencies":[]
}
Simply fills all the useful information for the correct loading of the mod. Nothing weird here
Example 2: Trumpet Factory[]
Downloadable here.
Contents[]
- \Assets
- trumpetfactory.bundle (Asset Bundle created using our tools for packing game data)
- \Code
- (empty)
- \Content
- TrumpetFactory.json (data for the building itself)
- TrumpetFactoryUnlock.json (unlocks the building if the Trumpet research is done)
- TrumpetProduct.json (adds the Trumpet product)
- TrumpetRecipe.json (adds the Recipe to create the Trumpet)
- TrumpetUnlock.json (allows for the Trumpet to be unlocked in the Tech Tree
- desc.json (description of the mod)
TrumpetFactory.json[]
{
"type":"ProjectAutomata.Building",
"object":{
"contextBasedUI":"BuildingPanel",
"topoXMinOffset":-3,
"topoXMaxOffset":2,
"topoYMinOffset":-3,
"topoYMaxOffset":0,
"restrictions":[
"Can Afford",
"Can Build At",
"Influent Enough",
"Owns Permit",
"Factory Settlement Distance",
"Not Too Close To Any Resource"
],
"nameFormat":"%n %i",
"parkingPathTransforms": [
"TrumpetFactoryVis"
],
"connections":[
{
"x":0,
"y":0,
"direction":"South",
"tilesAmt":2,
"tier":1,
"invisible":false,
"impliesConnectivities":true,
"connectionType":"DEFAULT",
"network":"Road"
}
],
"visualization":"TrumpetFactoryVis",
"buildingName":"Trumpet Factory",
"buildingPanelName":"Trumpet Factory",
"description":"A factory for a very special instrument",
"baseCost":150000,
"spawnDust":"Building_Dust_PS",
"uiOrder":0,
"category":"Factories",
"name":"TrumpetFactory"
},
"components":
[
{
"type":"ProjectAutomata.Factory",
"object":
{
"initialRecipe":"TrumpetRecipe",
"productionSpeed":1,
"availableRecipes":["TrumpetRecipe"]
}
},
{
"type":"ProjectAutomata.ProductSpecificProductStorage",
"object":
{
"slots":50
}
},
{
"type":"ProjectAutomata.BuildingRequirementController",
"object":
{
}
},
{
"type":"ProjectAutomata.Upkeep",
"object":
{
"monthlyUpkeep": 0.25
}
},
{
"type":"ProjectAutomata.BuildingLogistics",
"object":
{
}
},
{
"type":"ProjectAutomata.RecipeUserRequireRecipeSelected",
"object":
{
}
},
{
"type":"ProjectAutomata.BuildingRequirementStorageSpace",
"object":
{
"notification": "Storage Full"
}
},
{
"type":"ProjectAutomata.JITIVehicleFleet",
"object":
{
"vehiclePrefab": "BeweryTruck",
"maximumVehicleAmount": 10
}
},
{
"type":"ProjectAutomata.BuildingRequirementJobDelegatorError",
"object":
{
}
}
]
}
TrumpetFactoryUnlock.json[]
{
"type":"ProjectAutomata.TechTreeBuildingUnlock",
"object":
{
"name":"TrumpetFactoryUnlock",
"displayName":"Trumpets!",
"description":"Unlocks trumpets",
"unlockedByDefault": true,
"tier": 2,
"requiredUnlocks": [],
"building": "TrumpetFactory"
}
}
TrumpetProduct.json[]
{
"type":"ProjectAutomata.ProductDefinition",
"object":
{
"name":"Trumpet",
"displayName":"Trumpet",
"description":"A very special instrument :>",
"category":"End Products"
}
}
TrumpetRecipe.json[]
{
"type":"ProjectAutomata.Recipe",
"object":
{
"name":"TrumpetRecipe",
"displayName":"Trumpets",
"description":"A very special instrument :>",
"gameDays": 30,
"excludeInRecipeGraph": false,
"allowWaterResources": false,
"requiredModules": [],
"requiredResourceNodeProducts": [],
"ingredients": [{"productName": "IronOre", "amount": 1},{"productName": "Copper", "amount": 1}],
"result": [{"productName": "Trumpet", "amount": 1}]
}
}
TrumpetUnlock.json[]
{
"type":"ProjectAutomata.TechTreeRecipeUnlock",
"object":
{
"name":"TrumpetUnlock",
"displayName":"Trumpets!",
"description":"Unlocks trumpets",
"unlockedByDefault": true,
"tier": 2,
"requiredUnlocks": ["TrumpetFactoryUnlock"],
"recipes": ["TrumpetRecipe"]
}
}
desc.json:[]
{
"author":"Dapper Penguin Studios",
"name":"Trumpet",
"version":1,
"versionString":"1.0.0",
"dependencies":[]
}