Jump to content

6 posts in this topic Last Reply

Highlighted Posts

Posted:
Last Online:  
 

In this tutorial, we will replicate the functionality of the Show Limits mod. Like in Tutorial 1, we will use the default alert window to display the limits. You will learn how to use ILSpy for reverse engineering, how to access singleton manager objects, how to work with building/tree instances and how to listen to keyboard shortcuts.

 

Step 1: Exploring the game source code in ILSpy

Download the latest version of ILSpy (select "Download Binaries"). ILSpy is an open source decompiler that allows you to take a look at the source code of the game and other mods. It's basically the opposite of a compiler:

[compiled .dll file]   →   [DECOMPILER]   →   [.cs files containing raw C# code]

The restored source code is not exactly the same as the original code. Names of variables defined in methods are lost, which makes the code harder to read. Some parts of the loading code are not readable (enumerators), but that won't bother us.

Open the Assembly-CSharp.dll file in ILSpy (C:\Program Files (x86)\Steam\steamapps\common\Cities_Skylines\Cities_Data\Managed). On the left, expand the source tree:

ouo0lrK.png

The tree contains all classes and structs of the game. Select one of them to view the source code. There are different types of classes, here are the most common ones:

  • ...Info: Prefabs or other assets (see Tutorial 1 and 2)
  • Building, Citizen, District, NetLane, NetNode, NetSegment, PropInstance, TransportLine, TreeInstance, Vehicle, ZoneBlock (structs): Instances of objects, managed by a simulation system. There is a limit how many instances of a certain type can exist.
  • ...AI: AI behaviour attached to prefabs, calculating the behavior of object instances.
  • ...Manager: A simulation system,  responsible for rendering, simulation and creation/deletion of instances
  • ...Tool: Tool for interacting with the city
  • ...Collection: A collection of prefabs or assets
  • ...Properties: Properties for simulation systems, like colors, shaders and constants
  • ...Panel: UI elements
  • ...Wrapper: Implementation of the official modding API

 

The various manager classes are the key components of the game. Managers extend the Singleton<> class. The singleton pattern is a software design pattern that ensures that only one instance of a class exists. You can easily access a singleton in Cities: Skylines with the static instance field:

string cityName =  SimulationManager.instance.m_metaData.m_CityName;
Debug.Log("Name of the city: " + cityName);

Alternatively:

Singleton<SimulationManager>.instance

Tip: You can run these code snippets with the ModTools console (F7)

 

Today we will take a look at the TreeManager and BuildingManager to display the number of trees and buildings.

Tree Manager

Select the TreeManager in ILSpy. After scrolling down a few lines in the source code, you will find the constant MAX_TREE_COUNT and the integer field m_treeCount:

govFdtM.png

Outputting these numbers will lead to the desired result. We will go a step further and check how the m_treeCount field is calculated. Right click the m_treeCount item in the left sidebar and select "Analyze":

yzYCBLC.png

The panel at the bottom displays which methods are reading the field, and which methods are assigning values to the field. For example, the AssetEditorChirpPanel and EditorChirpPanel are reading the field to display the number of trees in the asset/map editor.

The field is modified by 3 methods of the TreeManager:

  • CreateTree: When a new tree instance is placed, increment the value by 1.
  • ReleaseTreeImplementation: When a tree instance is deleted, decrement the value by 1
  • Data.AfterDeserialize: After a save game is loaded, recalculate the tree count

The last method is particularly interesting. Double click it to view the source code of the method:

BaIzBmC.png

The method calls TreeManager.instance.m_trees.ItemCount() to calculate the number of trees.

m_trees is a special kind of array list (Array16<TreeInstance>) that is used by the game to manage instances of trees, props, buildings etc. The list contains a fixed number of TreeInstance structs (262144 in case of trees). When a new tree is created, one of the unused items from the listt is activated and used to describe the state, position and type of a tree. A few lines above you can see how the game iterates through the list to modify the active items.

Tip: You can also use the "Analyze" feature on methods and classes. It will help you to understand how the different components of the game are connected.

Building Manager

Now, select the BuildingManager on the left. You will notice that the BuildingManager contains the same kind of fields:

  • const int MAX_BUILDING_COUNT: The max number of building instances
  • int m_buildingCount: The current number of building instances
  • Array16<Building> m_buildings: The array list containing the building instances

 

Step 2: Outputting the limits with ModTools

Before writing our mod, we will create a ModTools script to see if everything works:

int treeCount = TreeManager.instance.m_treeCount;
int maxTreeCount = TreeManager.MAX_TREE_COUNT;

Debug.Log("Number of trees: " + treeCount + " of " + maxTreeCount);

int buildingCount = BuildingManager.instance.m_buildingCount;
int maxBuildingCount = BuildingManager.MAX_BUILDING_COUNT;

Debug.Log("Number of buildings: " + buildingCount + " of " + maxBuildingCount);

Load or create a city and open the ModTools console (F7). In the bottom of the console, paste the script and press the "Run" button:

nmIxvkd.jpg

 

Step 3: Project Setup

Create a new project named "ShowLimits" in VS2017, following Method 2 in Tutorial 0 (working with a text editor is still possible in this tutorial, but not recommended)

Your IUserMod implementation should look like this:

using ICities;

namespace ShowLimits
{
    public class ShowLimitsMod : IUserMod
    {
        public string Name => "Show Limits";
        public string Description => "Displays the number of buildings and trees in your city (Hotkey CTRL + I)";
    }
}

 

Step 4: Threading Hook & Hotkeys

The IThreadingExtension/ThreadingExtensionBase is another hook provided by the official modding API. Classes implementing IThreadingExtension are invoked before, during and after every simulation tick (many times per second). It is important that the contained code is very light-weight (no IO operations, like reading configuration files, no object construction...).

We will use the hook to check if the player is pressing they keyboard shortcut (CTRL + I).

Create a new class called ShowLimitsThreading:

using ColossalFramework.UI;
using ICities;
using UnityEngine;

namespace ShowLimits
{
    public class ShowLimitsThreading : ThreadingExtensionBase
    {
        private bool _processed = false;

        public override void OnUpdate(float realTimeDelta, float simulationTimeDelta)
        {
            if ((Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)) && Input.GetKey(KeyCode.I))
            {
                // cancel if they key input was already processed in a previous frame
                if (_processed) return;

                _processed = true;

                // compose message
                int treeCount = TreeManager.instance.m_treeCount;
                int maxTreeCount = TreeManager.MAX_TREE_COUNT;

                int buildingCount = BuildingManager.instance.m_buildingCount;
                int maxBuildingCount = BuildingManager.MAX_BUILDING_COUNT;

                string message = $"Trees: {treeCount} of {maxTreeCount}\nBuildings: {buildingCount} of {maxBuildingCount}";

                // display message
                ExceptionPanel panel = UIView.library.ShowModal<ExceptionPanel>("ExceptionPanel");
                panel.SetMessage("Show Limits", message, false);
            }
            else
            {
                // not both keys pressed: Reset processed state
                _processed = false;
            }
        }
    }
}

g9NaXJ7.png

Now compile the mod (F6). If there are any compilation errors, use the error list in VS2017 to locate and fix the error (View > Error List)

When the compilation was successful, run the game. Enable the mod in content manager (if your mod does not show up, you probably forgot to setup the post build script).

Now create or load a city. Press Ctrl + I:

u2gKcRZ.jpg

Happy Coding!

Download Source

 

Next Part:

 

  • Like 3

Share this post


Link to post
Share on other sites
Posted:
Last Online:  
 

I'm loving all your tutorials so far. Great job! One question is it possible for you to do a guide on maintaining a mod? For example mods break when a new version is updated, could you please show how you might resolve this or is it more complicated than that?

Share this post


Link to post
Share on other sites
  • Original Poster
  • Posted:
    Last Online:  
     
    1 hour ago, barcasam7 said:

    I'm loving all your tutorials so far. Great job!

    Thank you! I'm afraid that not too many people will find these tutorials.

    1 hour ago, barcasam7 said:

    One question is it possible for you to do a guide on maintaining a mod? For example mods break when a new version is updated, could you please show how you might resolve this or is it more complicated than that?

    It's not that hard. Mods break because the code of the game or Unity changes, that means fields and methods you are referencing no longer exist. Visual Studio will highlight which code statements are no longer valid:

    BBER655.png

    For mods utilizing detours (explained in one of the next tutorials), it can be a little bit harder because Visual Studio does not highlight detour-related errors. In that case, you have to compare the old game source code with the new game source code with a tool like Diff Checker, then adapt your mod to the changes.

    In general, the ModTools console and the output_log.txt will help you to locate errors occuring at runtime. Errors will look like this:

    Object reference not set to an instance of an object [System.NullReferenceException]
    
    Details:
    No details
    System.NullReferenceException: Object reference not set to an instance of an object
    at BuildingThemes.Detour.ZoneBlockDetour.SimulationStep (UInt16 blockID) [0x00000] in <filename unknown>:0
    at ZoneManager.SimulationStepImpl (Int32 subStep) [0x00000] in <filename unknown>:0
    at SimulationManagerBase`2[Manager,Properties].SimulationStep (Int32 subStep) [0x00000] in <filename unknown>:0
    at ZoneManager.ISimulationManager.SimulationStep (Int32 subStep) [0x00000] in <filename unknown>:0
    at SimulationManager.SimulationStep () [0x00000] in <filename unknown>:0
    at SimulationManager.SimulationThread () [0x00000] in <filename unknown>:0 

    What you see there is a stack trace showing which nested method calls lead to the error. In this case, the error was caused in the method BuildingThemes.Detour.ZoneBlockDetour.SimulationStep. More specificity, the method tried to access a property/method of a variable that was set to null (probably the most common exception).

    Sadly the game does not tell you in which line the error occurred, but it's usually easy to find out. In the worst case, you have to add a few log calls to find out where the error occurred:

    public void MethodThatFails() {
        
        doSomething();
      
        Debug.Log("checkpoint 1");
      
        BuildingInfo prefab = PrefabCollection<BuildingInfo>.FindLoaded("<invalid name>");
      
        Debug.Log("checkpoint 2");
      
        prefab.m_placementMode = BuildingInfo.PlacementMode.Roadside; // error occurs here because prefab is null
      
        Debug.Log("checkpoint 3");
      
        doSomethingElse();
    }

    The log would look like this:

    checkpoint 1
    
    checkpoint 2
    
    Object reference not set to an instance of an object [System.NullReferenceException]
    
    Details:
    No details
    System.NullReferenceException: Object reference not set to an instance of an object
    at YourMod.YourClass.MethodThatFails () [0x00000] in <filename unknown>:0
    at ...

     

    • Like 2

    Share this post


    Link to post
    Share on other sites
    Posted:
    Last Online:  
     
    8 hours ago, boformer said:

    Thank you! I'm afraid that not too many people will find these tutorials.

    I haven't had time to start doing the tutorials yet (rn I'm taking a bunch of city planning courses online just for C:S) , but I will in the next few weeks and I can tell you now, what you're doing is absolutely invaluable. Before these tutorials, honestly the resources were very confusing and I had no idea where to begin. Now, I feel as if I can really contribute. Thank you and please continue making these tutorials :)

    • Like 1

    Share this post


    Link to post
    Share on other sites
    Posted:
    Last Online:  
     

    Thanks for the tutorial. Do you know how it is with control key on operating systems other than windows?

    if ((Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)) && Input.GetKey(KeyCode.I))

    is this ok for mac and linux users?

    Share this post


    Link to post
    Share on other sites
  • Original Poster
  • Posted:
    Last Online:  
     
    18 hours ago, Strdate said:

    Thanks for the tutorial. Do you know how it is with control key on operating systems other than windows?

    
    if ((Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)) && Input.GetKey(KeyCode.I))

    is this ok for mac and linux users?

    I don't think that Linux users are using different keyboards. I'm not sure about Apple. The Input/KeyCode stuff is right from Unity, so you will probably find an answer on the Unity website.

    • Like 1

    Share this post


    Link to post
    Share on other sites

    Sign In or register to comment...

    To comment in reply, you must be a community member

    Sign In  

    Already have an account? Sign in here.

    Sign In Now

    Create an Account  

    Sign up to join our friendly community. It's easy!  

    Register a New Account


    ×

    Thank You for the Continued Support!

    Simtropolis depends on donations to fund site maintenance costs.
    Without your support, we just would not be in our 24th year online!  You really help make this a great community. *:thumb:

    But we still need your support to stay online. If you're able to, please consider a donation to help us stay up and running. This helps sustain a platform where we can share our community creations for years to come.

    Make a Donation, Get a Gift!

    Expand your city with the best from the Simtropolis Exchange.
    Make a Donation and get one or all three discs today!

    STEX Collections

    By way of a "Thank You" gift, we'd like to send you our STEX Collector's DVD. It's some of the best buildings, lots, maps and mods collected for you over the years. Check out the STEX Collections for more info.

    Each donation helps keep Simtropolis online, open and free!

    Thank you for reading and enjoy the site!

    More About STEX Collections