Code Modding

From Software Inc.
Jump to navigation Jump to search

You can compile C# files or load dll libraries directly in Software Inc.

A good grasp of C# programming and the Unity3D API is required to make a code based mod.

Setup

Project setup

To create your own mod start a .NET Class Library project in Visual Studio, targeted at the .NET 4 profile. You should download and install the .NET 4 Framework if you don't have it, .NET Core is not the same.

You should add a reference to the following libraries: Note that you might need to add references to more, depending on what you want to do.

  • Software Inc_Data\Managed\UnityEngine.UI.dll
  • Software Inc_Data\Managed\UnityEngine.dll
  • Software Inc_Data\Managed\UnityEngine.CoreModule.dll
  • Software Inc_Data\Managed\Assembly-CSharp.dll
  • Software Inc_Data\Managed\Assembly-CSharp-firstpass.dll

Start by creating a class that implements the ModMeta abstract class, then implement as many classes that inherit from ModBehaviour as you want.

The ModMeta class will contain information about your mod and act as a manager for your mod.

namespace myMod 
{
  internal class MyModMeta : ModMeta
    {
        public override void ConstructOptionsScreen(RectTransform parent, bool inGame)
        {
            Text text = WindowManager.SpawnLabel();
            text.text = "This is the description of my mod\nIt shows in dropdown 'My Mod!' in the 'Mods' section of the options area.";
            WindowManager.AddElementToElement(text.gameObject, parent.gameObject, new Rect(0f, 0f, 400f, 128f),
                new Rect(0f, 0f, 0f, 0f));
        }

        public override string Name => "My Mod!";
    }
}

ModBehaviours are just a subclass of Unity's MonoBehavior and work the same way, i.e. they use the same life cycle of Awake, Start, Update, OnDestroy, ModBehaviours only differ in that they have the abstract OnActive and OnDeactivate methods, to signal when the mod is toggled. Each ModBehaviour implementation will be instantiated once the mod is loaded.

Compile

When you're done, create a subfolder for your mod in a folder called "DLLMods" in the game's root directory and put your compiled .dll file in it, or .cs files if you want the game to compile them for you. Note that if you let the game compile the C# files for you, you are limited to C# version 3, but using the game's compiler is required if you want to upload your mod to the Steam Workshop.

If you use the game's compiler, note that it will inject error handling code into each function body, you can disable this behaviour while you are developing your mod using the launch paramater -DisableModErrors.

Please note that due to a bug in the compiler the game uses, you cannot use enums if you are using straight .cs files or the game will crash

Example mod

Here's a working mod example that lets you change how many floors you can build on, complete with comments: Floor Mod (Updated to work with Alpha 10.7+)

Dependencies

If you want to reference external dll files, you can put them in the Software Inc_Data\Managed folder.

You can also add this code snippet to your ModMeta class, to load dll files from a subfolder: (Note that this requires GiveMeFreedom and it might not find the correct dll filename, so you might need to adjust it to your situation)

public override void Initialize(ModController.DLLMod parentMod)
    {
    AppDomain.CurrentDomain.AssemblyResolve += (x, y) => Assembly.LoadFrom(Path.Combine(parentMod.FolderPath(), "SubFolder/" + y.Name.Substring(0, y.Name.IndexOf(",")) + ".dll"));
    base.Initialize(parentMod);
    }

Compatibility

From Beta 1.7+ you can use define symbols for non-dll-based mods that are compiled by the game, e.g.

#if !SWINCBETA && !SWINCRELEASE
    (this is beta pre 1.7)
#elif SWINCBETA1_7 || SWINCBETA1_8
    (Something broke in beta 1.9, so this is sectioned off)
#elif SWINCBETA1_9 || SWINCBETA1_10
    (Something broke in beta 1.11, so this is sectioned off)
#else
    (This is for all future versions beta 1.11+)
#endif

The game will define 3 symbols SWINCTYPE (SWINCBETA or SWINCRELEASE), SWINCTYPEMAJOR (SWINCBETA1, SWINCBETA2, etc.) and SWINCTYPEMAJOR_MINOR (SWINCBETA1_7, SWINCBETA1_8, etc.)

Getting Started

Debugging console

Before you do anything, you should enable the in-game debugging console by binding the console key at the bottom of the key binding menu in the options window. This will help you debug your mod by giving you error messages and enabling the RECOMPILE_DLL_MOD, RELOAD_DLL_MOD and UNLOAD_DLL_MOD commands.

Helpful commands

The EXECUTE command will allow you to execute arbitrary SIPL code (which closely resembles C#) while the game is running. E.g. if you wanted to find the highest paid employee you could write EXECUTE GameSettings.sActorManager.Actors.OrderByDescending(x.employee.Salary).First() or if you wanted to color all selected rooms' exterior green you could write EXECUTE Selected.Where(x is Room).ForEach(x.OutsideColor = Color(0,1,0)).

Here are some handy variables and functions you can use with EXECUTE:

GameSettings References the currently active instance of GameSettings
SelectorController References the currently active instance of SelectorController
MarketSimulation References the currently active instance of MarketSimulation
Selected List of objects currently selected in-game
LastProduct Reference to last product opened in a detail window
LastCompany Reference to last company opened in a detail window
WorkItems List of the active player company's tasks (GameSettings.MyCompany)
Now Current in-game date
CopyToClipboard(o) Copies o to the clipboard, wrap your entire statement in this to put the result in the clipboard
DoSelect(o) Selects o in-game, can be an object or a list, e.g. DoSelect(GameSettings.sActorManager.Actors)

The SHOW_INSPECTOR command opens a window that allows you to inspect all objects in the active scene.

Note on threading

You should stick to the ModBehaviour's Update method, rather than trying to write your own update loop with threads or timers, as you cannot interact with Unity's system from other threads and you will most likely end up causing race conditions when interacting with the game's systems. You can use Unity's Time class to keep track of elapsed time between frames.

Saving and loading data

Global data

All ModBehaviours have SaveSetting and LoadSetting methods to save data globally.

Note that SaveSetting and LoadSetting are generic and will call ToString() and try to convert the saved string back to the data type you specify when loading, so this will only work for simple types, like integers, floats, doubles, bools, strings, etc.

To load something back, use LoadSetting<type>("Key", defaultvalue), e.g. LoadSetting<bool>("Notifications", false) or LoadSetting("Notifications", false), since the type bool is implicit from using false.

There's also TryLoadSetting, if you want to handle when the setting doesn't exist and DeleteSetting to remove existing settings.

Per save data

You can also save data to the player's save file, to keep settings per save, by overriding your ModBehaviours' Serialize and Deserialize methods. These use a WriteDictionary, which is just a dictionary of whatever you want, and the game will handle turning it into bits.

The WriteDictionary is powerful, in that it has no limitations on the data you can save, but please note that any custom classes saved using this method has to have an empty constructor. If you want some fields not to be serialized, use the [System.NonSerialized] attribute. Properties are not serialized, so you need to create backing fields. Saving custom classes from your mod will also break saves if the mod is no longer installed, so it's best to avoid when you can.

When you want to save data, override and populate the WriteDictionary with values in the Serialize(WriteDictionary data, GameReader.LoadMode mode) method, this will then be returned when a save is loaded in the Deserialize(WriteDictionary data, GameReader.LoadMode mode) method. If you wanted to save a setting you can write data["Notifications"] = true and load it back with Notifications = data.Get("Notifications", true)

There is also a LoadMode parameter, which tells you whether it was a full save, a build mode save or a company save (Usually used when the player is moving to another map or during multiplayer synchronization), which will allow you to only save data needed for each type. In most cases it's best to avoid saving anything for a build mode save.

You can also override the serialization methods in the ModMeta class to take complete control of saving data. Returning null will make the mod not save any data. To help you with serializing data from your ModBehaviours, the ModMeta class has a ModBehaviours list of all active ModBehaviours that were created when the mod was first loaded. However, the default behaviour is to call Serialize and Deserialize on your ModBehaviours in turn and check if they saved any data, so just leaving it as is will work in most cases.

Note that your the save data is identified by the Name you've set in the ModMeta class. If you change this name, it will no longer be able to find the correct data from older saves and name clashes will result in data loss.

Reading external files

All modbehaviours have access to their ParentMod, this is the class that manages your mod, and through this you can load files using the methods LoadTexture(png, jpg, jpeg), LoadTydFile(tyd), LoadAudio(mp3, wav, ogg), LoadGLTF(gltf, glb, only loads the first mesh and its morph targets) and LoadOBJ(obj). Note that these methods all use relative paths from where your mod is installed and using ../ won't work.

Full access

By default, certain namespaces and types are off-limits to mods for security reasons. If you want to make a mod that writes to files or accesses the internet, you need to put a public static bool called GiveMeFreedom in your ModMeta implementation. Note that this only works for dll-based mods, which can't be uploaded to the Steam Workshop, and the user will be warned.

Events

As of Alpha 11.6.5.

Note that no MarketSimulation events are raised during the initial market simulation to avoid race conditions.

GameSettings.IsDoneLoadingGame Raised when a new game has finished loading. Beware that this can be raised before the initial market simulation is done.
GameSettings.GameReady Raised when the game is guaranteed to be finished loading and the player has full control.
GameSettings.OnQuit When the player quits an active game.
GameSettings.Instance.OnServersChanged When servers have been added or removed. Usually used to keep server dropdowns updated
MarketSimulation.OnProductReleased + OnAddOnReleased When a product is released, but does not count Mock products, which are products that are only available to support during beta for the player.
MarketSimulation.OnProductRemoved + OnAddOnRemoved When a product is removed. Products are usually removed from the game after 20 years of inactivity to keep the save file from bloating.
MarketSimulation.OnCompanyFounded When a new company is founded, not including the player's company.
MarketSimulation.OnCompanyClosed When a new company is closed down.
MarketSimulation.OnTechResearched When new a new tech level is researched, not including the initial tech levels.
MarketSimulation.OnFrameworkReleased When a framework is released.
TimeOfDay.OnHourPassed When an hour has passed, after everything has updated (server refresh, etc.).
TimeOfDay.OnDayPassed When a day has passed, after everything has updated (market simulation, etc.). This is called right before OnMonthPassed when a month passes, so it is basically the same as OnMonthPassed with 1 days per month, except it happens before bills are processed.
TimeOfDay.OnMonthPassed When a month has passed, after everything has updated (bills, etc.).

Entry points

GameSettings.Instance This class contains most of the objects that manage the game
GameSettings.Instance.MyCompany The player's company
MarketSimulation.Active Manages all companies and products
GameSettings.Instance.sRoomManager Manages rooms, furniture and room segments
GameSettings.Instance.sActorManager Manages employees and teams
SelectorController.Instance Manages selection
TimeOfDay.Instance Manages time
HUD.Instance Manages the main HUD and windows
ObjectDatabase.Instance Contains all furniture and room segments
WindowManager Controls windows and has functions to create windows and GUI elements