Jump to content

19 posts in this topic Last Reply

Highlighted Posts

Posted:
Last Online:  
 

In this tutorial, we will create a simple mod that removes some of the ugly vanilla billboard props. You will learn how to work with loading hooks, prefab collections and the ModTools scene explorer.

 

Step 1: Project Setup

Create a new project named "CityBeautifier" 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 CityBeautifier
{
    public class CityBeautifierMod : IUserMod
    {
        public string Name => "City Beautifier";
        public string Description => "Removes some ugly props";
    }
}

Tip: The arrow syntax (=>) is a shorthand for the property syntax (get { return ...; }) used in the 0. tutorial. Both are equivalent.

 

Step 2: Loading Hook

The Mod API of Cities: Skylines contains various extension interfaces. You can create classes implementing these interfaces to hook into the game.

The most important interface is ILoadingExtension. A class implementing this interface will get notified when the loading process begins and when it completes, and when a save is unloaded.

Create a new file called CityBeautifierLoading.cs in the Solution Explorer. Add the following content to the file:

using ColossalFramework.UI;
using ICities;

namespace CityBeautifier
{
    public class CityBeautifierLoading : ILoadingExtension
    {
        // called when level loading begins
        public void OnCreated(ILoading loading)
        {
        }
        
        // called when level is loaded
        public void OnLevelLoaded(LoadMode mode)
        {
            // create dialog panel
            ExceptionPanel panel = UIView.library.ShowModal<ExceptionPanel>("ExceptionPanel");

            // display a message for the user in the panel
            panel.SetMessage("City Beautifier", "The level is loaded!", false);
        }

        // called when unloading begins
        public void OnLevelUnloading()
        {
        }

        // called when unloading finished
        public void OnReleased()
        {
        }
    }
}

Tip: You can also extend the class LoadingExtensionBase. It allows you to override a single loading method instead of implementing all methods

 

n7pl2Uf.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. You will see the dialog panel as soon as the loading screen disappears:

4unPNrM.png

Close the game (UI development requires a complete restart after every change)

 

Step 3: Working with the Prefab Collection

Our goal is the removal of various props which are attached to buildings. To do that, we have to access the collection of loaded buildings, more specifically building prefabs.

Prefabs vs. Instances: Building prefabs are templates for building instances. That means when a building instance is spawned, it uses the properties, textures and models defined in the building prefab. Modifying the building prefab usually causes all building instances to change. There are also prefabs for trees, props, vehicles and networks. Prefab classes of the game always end with Info (e.g. BuildingInfo, PropInfo, ...).

All building prefabs can be found in the PrefabCollection<BuildingInfo>. We will now learn how to cycle through all buildings in this collection.

Replace the OnLevelLoadedMethod with this code, then compile the mod and run the game:

public void OnLevelLoaded(LoadMode mode)
{
    // total number of loaded building assets
    int buildingPrefabCount = PrefabCollection<BuildingInfo>.LoadedCount();

    // create a message string for the user
    string message = "Number of building assets: " + buildingPrefabCount;

    // create dialog panel
    ExceptionPanel panel = UIView.library.ShowModal<ExceptionPanel>("ExceptionPanel");

    // display the message in the panel
    panel.SetMessage("City Beautifier", message, false);
}

This will output the total number of loaded building prefabs:

bEfHql2.png

 

Now we will create a list of all building names, using a simple for loop. The LoadedCount() method returns the total number of prefabs. We can use the GetLoaded(uint) method to get the prefab objects, using a number in range 0 <= index < buildingPrefabCount:

public void OnLevelLoaded(LoadMode mode)
{
    int buildingPrefabCount = PrefabCollection<BuildingInfo>.LoadedCount();

    string message = "Number of building assets: " + buildingPrefabCount;

    // append a list of building names to the message
    for(uint index = 0; index < buildingPrefabCount; index++)
    {
        // get the building asset with the given index from the collection
        BuildingInfo prefab = PrefabCollection<BuildingInfo>.GetLoaded(index);

        // append a separator and the name of the asset to the message
        message += ", ";
        message += prefab.name;
    }

    ExceptionPanel panel = UIView.library.ShowModal<ExceptionPanel>("ExceptionPanel");
    panel.SetMessage("City Beautifier", message, false);
}

The output looks like this:

jTcE9Br.png

We learned how to access the building prefabs. In the next step, we will use ModTools to dive deeper into the structure of BuildingInfo objects.

 

Step 4: Exploring and modifying building prefabs with the ModTools scene explorer

Subscribe to ModTools and enable the mod in content manager. Create or load a city and click on a building you want to inspect. Then use the "Find in Scene Explorer" button.

I picked one of the commercial buildings that is decorated with ugly billboards:

u5frlnD.jpgYXaY0h5.png

The properties displayed in the scene explorer belong to the building instance. Props are defined in the building prefab. Luckily the prefab can be found right at the top (expand the "Info" property). Scroll down and expand the "m_props" property. You will see a list of prop items:

D4otRzm.png

Expand the items of the list to find out more about the props. Here you can change properties like position, probability and angle.

Find the prop you want to remove and set the probability to 0:

oQeAv37.jpgOc1jzCA.jpg

The prop will disappear instantly. Repeat the step for all props you want remove. Create a list of all prop names, like this:

Billboard_big_variation_02
Billboard_big_variation_04
neon-yakisoba-noodles
neon-burned-bean-coffee
neon-morellos
Billboard_big_variation
Billboard_big_variation_01

Problem: The changes you applied to the prefabs are not permanent because prefabs are loaded from game/.crp files when you load your city. In the next step, we will change the probability values programmatically, in the loading class of our mod.

 

Step 5: Modify building props programmatically

We will remove the panel/message used in Step 3. Instead, we will add another nested for loop to cycle through all prop items of every building prefab:

public void OnLevelLoaded(LoadMode mode)
{
    for(uint index = 0; index < PrefabCollection<BuildingInfo>.LoadedCount(); index++)
    {
        // get the building prefab with the given index from the collection
        BuildingInfo prefab = PrefabCollection<BuildingInfo>.GetLoaded(index);

        // skip prefabs without props
        if(prefab == null || prefab.m_props == null) continue;

        for (int propIndex = 0; propIndex < prefab.m_props.Length; propIndex++)
        {
            // get the prop item
            BuildingInfo.Prop propItem = prefab.m_props[propIndex];

            // skip trees/undefined props etc.
            if (propItem.m_prop == null) continue;

            string propName = propItem.m_prop.name;

            // check if the prop name equals one of the banned props
            if (propName == "Billboard_big_variation"
                || propName == "Billboard_big_variation_01"
                || propName == "Billboard_big_variation_02" 
                || propName == "Billboard_big_variation_04"
                || propName == "neon-yakisoba-noodles"
                || propName == "neon-burned-bean-coffee"
                || propName == "neon-morellos")
            {
                // set the probability to zero to hide the props
                propItem.m_probability = 0;
            }
        }
    }
}

The continue keyword is a control statement of the for loop. It skips the execution of the current iteration and moves along to the next iteration of the loop.

Tip: Instead of using the equals operator (==), you can use the Contains method to detect all prop names containing a certain string:

if (propName.Contains("Billboard_big") || propName.Contains("neon-"))
{
    propItem.m_probability = 0;
}

 

The result (before-after):

8VyO15u.jpgaMo7KSg.jpg

2B76GKh.jpghK3w8g7.jpg

You can see that the mod removes the billboards from all buildings. We reached our goal!

Happy Coding!

 

Tip: You can also remove trees with the same technique:

if (propItem.m_tree == null) continue;
string treeName = propItem.m_tree.name;

Tip: You can limit the replacement to buildings with a certain name, or buildings of certain zone type:

for(uint index = 0; index < PrefabCollection<BuildingInfo>.LoadedCount(); index++)
{
    BuildingInfo prefab = PrefabCollection<BuildingInfo>.GetLoaded(index);
    if(prefab == null || prefab.m_props == null) continue;
  
    // filter by name
    if(!prefab.name.Contains("Blockhouse")) continue;
  
    // filter by building/service type
    if (prefab.m_class.m_service != ItemClass.Service.Industrial) continue;
  
    // ...
}

 

Next part:

 

  • Like 9

Share this post


Link to post
Share on other sites
  • Original Poster
  • Posted:
    Last Online:  
     

    It would be great if you could provide feedback! Is the tutorial too hard, too easy? Did you experience any problems?

    What would you like to see next?

    Share this post


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

    Nice! Will there be tutorials on how to alter AI for e.g. traffic, city services, transit, pedestrians (or anything really)?

    Share this post


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

    Nice! Will there be tutorials on how to alter AI for e.g. traffic, city services, transit, pedestrians (or anything really)?

    I plan to make one that modifies the Park Building AI (seagull remover).

    Is there something specific (and not too complex) you would like to alter?

    PS: Is anyone willing to convert this tutorial to Steam BBCode, so I can publish it as a Steam Guide?

    Share this post


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

    @boformer

    Instead of showing how to remove seagulls would it be an option to show that on a mod that removes the buoy of the ferry lines. If you have many ferry lines in a small area it looks like after the titanic has sunken and a lot of people still swimming. So it would be a example and at the same time resolving an issue with the game.

    Share this post


    Link to post
    Share on other sites
  • Original Poster
  • Posted:
    Last Online:  
     
    33 minutes ago, rovapasi1 said:

    @boformer

    Instead of showing how to remove seagulls would it be an option to show that on a mod that removes the buoy of the ferry lines. If you have many ferry lines in a small area it looks like after the titanic has sunken and a lot of people still swimming. So it would be a example and at the same time resolving an issue with the game.

    The next tutorial (replacing road trees) will cover your needs. The buoys are props ("Nautical Marker") which are attached to the ferry line network, and you can remove them just like building props.

    Share this post


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

    Is there something specific (and not too complex) you would like to alter?

    Well, I'm personally biased as you know but I'd love to see how the transit AI works :P

    I think what I'd love to learn, even if you can't make a full mod out of it as an example, is how the different AI systems work -- for example, "this is the transit AI, this class does this, this argument does that, this section of code is responsible for x y z" etc. Like basically an introduction to the different AI systems and how each work so that then we can experiment on our own, using the methods you teach in this and other upcoming tutorials. If possible, that would be perfect :D


      Edited by alborzka  

    Share this post


    Link to post
    Share on other sites
    Posted:
    Last Online:  
     
    53 minutes ago, boformer said:

    The next tutorial (replacing road trees) will cover your needs. The buoys are props ("Nautical Marker") which are attached to the ferry line network, and you can remove them just like building props.

    That sounds cool. THANKS!

    Share this post


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

    Well, I'm personally biased as you know but I'd love to see how the transit AI works :P

    I think what I'd love to learn, even if you can't make a full mod out of it as an example, is how the different AI systems work -- for example, "this is the transit AI, this class does this, this argument does that, this section of code is responsible for x y z" etc. Like basically an introduction to the different AI systems and how each work so that then we can experiment on our own, using the methods you teach in this and other upcoming tutorials. If possible, that would be perfect :D

    I'm not an expert when it comes to AI programming, but at some point we will dive into reverse engineering/decompiling game source code and detours. At that point, you will be able to read and modify the AI code.

    7 hours ago, rovapasi1 said:

    That sounds cool. THANKS!

    Here it is:

     

    Share this post


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

    Boformer, your tutorials are perfect, clear and full of helping tips. So please, please make more of them :).

    When I look at game code I really struggle to find out which class, which property to modify, which method to call to achieve desired result.

    ILSpy do not show inherited properties from base classes, that do not help to find out which method modifies particular property for example. Are you having some sort of tips? How you discover which class property is influencing what?

    vl

    Share this post


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

    When I look at game code I really struggle to find out which class, which property to modify, which method to call to achieve desired result.

    ILSpy do not show inherited properties from base classes, that do not help to find out which method modifies particular property for example. Are you having some sort of tips? How you discover which class property is influencing what?

    ILSpy is a very interactive tool. Just click on the name of the base class to view it's properties. Use the "Analyze" feature to check how and where the properties are read or modified, like in tutorial 3:

    I'm just browsing the source code and the scene graph until I understand every single line of it. It's just a matter of time.

    Share this post


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

    Many thanks for writing this guide. I would like to try my hand at modding as well and your guide is very useful to help me get started. I am also new at c# so, I'll just have to learn harder.

    Using this tutorial (and a little peeking in existing mods) I was able to output some variables of buildings and change them such as electricity production for powerplants.

    For my next test I want to try to access information of the instances of buildings and get their output, like say accumulated garbage or something. For this I want to cycle through the instances in a city instead of the prefabs. It looks like you do something similar in the Make Historic tutorial, but I'm not ready for the serialize stuff yet and find the tutorial a little overwhelming. Can you tell me how to do this?

     

    I'm new to C# so I find your tutorials hard but not impossible. I do need to find mods on the workshop that do similar things in a very simplistic manner and crack them open with ILSpy to get more reference points. Maybe you can list some mods that are good learning materials for newbies as supplementary material for your tutorials. Once again: great work on the tutorials, I'll definately use them and see what I can build for C:S!

    Share this post


    Link to post
    Share on other sites
  • Original Poster
  • Posted:
    Last Online:  
     
    2 hours ago, reReticulated Spline said:

    Many thanks for writing this guide. I would like to try my hand at modding as well and your guide is very useful to help me get started. I am also new at c# so, I'll just have to learn harder.

    Using this tutorial (and a little peeking in existing mods) I was able to output some variables of buildings and change them such as electricity production for powerplants.

    I'm glad that it helped you!

    Quote

    For my next test I want to try to access information of the instances of buildings and get their output, like say accumulated garbage or something. For this I want to cycle through the instances in a city instead of the prefabs. It looks like you do something similar in the Make Historic tutorial, but I'm not ready for the serialize stuff yet and find the tutorial a little overwhelming. Can you tell me how to do this?

     

    Building[] buildingInstances = BuildingManager.instance.m_buildings.m_buffer;
    
    // itertate through all building instances, filter active
    for(ushort buildingId = 0; buildingId < buildingInstances.Length; buildingId++)
    {
        Building building = buildingInstances[buildingId];
                  
        if (building.m_flags != Building.Flags.None) // check if building instance is in use
        {
            // read values from building instance
            // you can get the prefab used by the instance with "building.Info"
        }
    }

    Please keep in mind that iterating through 64000 building instances is a heavy task which should only be executed when necessary (not every tick).

     

    Quote

    I'm new to C# so I find your tutorials hard but not impossible. I do need to find mods on the workshop that do similar things in a very simplistic manner and crack them open with ILSpy to get more reference points. Maybe you can list some mods that are good learning materials for newbies as supplementary material for your tutorials. Once again: great work on the tutorials, I'll definately use them and see what I can build for C:S!

    The mods shipped with the game, 25 Tiles, Catenary Replacer, Automatic Emptying, Control Building Level Up, sub-buildings enabler, Softer Shadows, No Seagulls by thale5, AVO (complex, but code is well done). Check the mod descriptions. In some cases, source code with comments is on GitHub.

    • Like 1

    Share this post


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

    So after copying and pasting the code, I get this error after saving. Do you know what caused it and how to fix it? (Im new to C#)

     

    hajskhj.PNG

    Share this post


    Link to post
    Share on other sites
  • Original Poster
  • Posted:
    Last Online:  
     

    @xXGamingViperXx

    What exactly did you copy? It looks like you copied code that belongs into a method into the class body, which is not valid C# code.

    You have to combine the code from Step 1, Step 2 and Step 5.

    Here is the complete runnable source code for reference: https://gist.github.com/boformer/45e3e2999d2f1aefb82048bc9cabe47c

    ------

    For the followup tutorials, you can download the source code from the STEX:

     

     

    Share this post


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

    Even copy/pasting the loading hook with a functional class inheriting the IUserMod interface, I CANNOT reproduce the window that pops up after loading a new city. Anything?

    Edit: It cannot be that only a single window can pop up after loading, can it?

     

    Share this post


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

    I know this topic is pretty old so maybe the way modding works has changed, however when I create something it shows up in the content manager but does nothing once i load a map. No panel is created.

    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