• Moose

MGB - Maxis Texture Replacement Tutorial and Dev Kit. v.1.0

(5.00)    (32 Reviews)

2 Screenshots

About This File

Do you use a Sidewalk mod that does not come with a Maxis Texture Replacer? Have you made your own custom sidewalks and want to extend them from the transit networks to your Lots? If the answer to either question is yes, you'll want to click download.

In essence this file is a tutorial with step by step instructions (Word Document) from simply replacing the three default Maxis plaza textures (10 minutes work) to complete removal of all of the default Maxis textures that use the Grey, White and Pink colour scheme.

============================================================================================================

NEW OPTION (MTRDK_Automated_Preview.zip) - JUN 2015

Due to a new technique, proposed by Rivit,, it is now possible to automate creating the textures in this set. You'll need the latest version of GoFSH to process them. If you select to download the file MTRDK_Automated_Preview.zip, you will find in the archive a texture repository with all the required files, including a script for use with GoFSH. For now, I will keep the old files available in case anyone prefers that. More information on the automated texture creation for SC4 can be found here if you are interested.

=============================================================================================================

Absolutely no modding experience is required to follow along, and included is my MTRDK (Maxis Texture Replacement Dev Kit) which includes pre-modified files that make creating replacements very easy. The screenshots above show the final results of this process with Paeng's Sandstone textures. This mod is an extension of my tutorial here on Sidewalks and how to get the most out of them.

You will need SC4 Tools to follow along (and a photo editor for the optional final part).

Hopefully someone will find this useful.


What's New in Version v.1.0


Released

  • Jun15 - Added optional Automated version for use with preview versions of GoFSH by Rivit.

CorinaMarie, Lapse, nos.17 and 2 others like this



User Feedback

Oh, the trolls have already been here and voted 1 star... how cute. :) Won't be much use, of course, as always... sad little creatures.

 

Back to topic, I haven't downloaded this yet, but I see that many people use sidewalk mods. However, most rely on pre-made ones that can simply be downloaded. Yout tutorial might enable more people to adjust a sidewalk mod to their liking, or even to make one from scratch. Plus, it may introduce people into texture editing as such - surely useful knowledge, and who knows where this knowledge may lead? :)

 

Have you thought about publishing this tutorial on the Omnibus? IMO, it's perfectly suited for this.

 

Anyway, thank you for sharing, and now I'm voting 5/5 to counter. :)

apeek, slickbg56, BIWDC and 1 other like this

Share this comment


Link to comment

Oh, the trolls have already been here and voted 1 star... how cute. :) Won't be much use, of course, as always... sad little creatures.

 

^^ Hehehe such sarcasm. Anyways, I think this is super cool to have and always thought it looked awkward having that white maxis texture bits alongside my override, especially for the overlays. Nice job. 5/5.

apeek likes this

Share this comment


Link to comment

Not sure if I'm going to need this but 5/5 on principle alone! Nice work and to whomever trolled this upload, unless you can do better, you have no business throwing up a one.

apeek likes this

Share this comment


Link to comment

Perfect Timing.  I was seriously considering taking this on myself.  Looks like I'll rap it up much quicker than I thought.

Share this comment


Link to comment

Your tutorial might enable more people to adjust a sidewalk mod to their liking, or even to make one from scratch. Plus, it may introduce people into texture editing as such - surely useful knowledge, and who knows where this knowledge may lead? :)

Until June I was frustrated with sidewalk mods, I liked all the different options out there but alas they have varying levels of uniformity depending on the mods available. I have a personal hatred of every pixel of Maxis white that invades my monitor. Therefore I decided to do something about it, the simple fact is that with the Japanese NAM facelift mod and the basic texture replacer a good chunk of the NAM can be made to fit any sidewalk, here's hoping the choices for users can be improved moving forwards. I have a number of sidewalk additions planned along the lines of my Sandstone Euro NAM mod.

Have you thought about publishing this tutorial on the Omnibus? IMO, it's perfectly suited for this.

That's something I will consider, right now I need to edit the tutorial for online use and I figured since the included resources were necessary it's just easier to keep the tutorial and files together.

Share this comment


Link to comment

Bravo sir. As stated above, I use the Paeng sandstone Mod (and it is great) - but this just enhances. I knew it could be done but I am still at novice level. Love that you made such a well put together "tutorial".

 

Really, REALLY looking forward to fixing those ugly overlays!!!

 

5/5

Share this comment


Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an Account  

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


Register a New Account

Sign In  

Already have an account? Sign in here.


Sign In Now
  • Announcements

    • Dirktator

      06/20/2017

      This month we're hoping for a little bit of a boost this month or next, in addition to covering the regular bills, we'd love to be able to do some hardware upgrades. STEX Collector's Set gifts can now be sent to you via digital download as well as regular mail if you wish! Every donation dollar goes toward keeping Simtropolis online and humming along. Your contribution is so meaningful, every dollar counts! Hardware Upgrades We've received a recommendation to consider some hardware upgrades to our ageing server. As our website community software improves with more and more features, and as we bolt on additional features such as chat, the increased load to handle all these new toys is taking the server a bit to task. You may have noticed that we'd been forced to temporarily disable the chat as we worked out allocating resources for more optimal performance. Following this, we've applied a number of 'soft' changes such as caching options and experimented with server configurations (nginx as proxy, for example), so the hardware aspect is also an area we'd like to focus on. The last time we upgraded hardware was back in 2012, prior to the release of SC13. Your contribution means a lot! Your donation will go toward helping us to 1.) migrate to a newer server or 2.) at least get some ram/cpu/storage updates and any other infrastructure hardware or services. Donate and Get a Gift or Donate Any Amount Thanks so much! - Dirktator & The Admins
    • Dirktator

      06/22/2017

      In order to conserve resources, we've disabled the global chatbar at the bottom of the site. Instead, the Chat can be accessed by clicking the top navigation tab. It will launch a new window. If you're not seeing this behavior, try clearing your browser's cache.
  • STEX Featured

  • Similar Content

    • By Ronyx69
      People have always asked for help by tagging me in this forum or messaging on steam or reddit.
      I am now on Patreon, so I figured I could give back more.
       
      This thread is:
      A discussion about my tutorials, scripts and source files.
      A place for anybody to get help with whatever you think I'm qualified to help with.  
      You can ask me about 3ds Max, Photoshop, ModTools, scripts, some basic modding and reverse engineering, game files... 
       
      I guarentee to respond in text or with a very quick and short "tutorial" video if it's appropriate.
      Do not request assets or ask extremely broad questions like "how to make assets" here.
       
      Asset Creation Videos (+ Theme & LUT)
       
      Modding Videos
       
      Tutorial Videos
       
      Scripts
       
      Asset Source Files
       
      Mod Source Code
       
      Quick Help Response Videos
       
    • By Cyclone Boom
      Cities: Skylines
      Index of Modding Tutorials & Information

      In an attempt to consolidate various tutorials & info on C:S modding, this topic will serve as an index.
      Basically, please post below links to anything aspiring modders may find useful!

      For example:
      Links to custom content related tutorials you've created in another topic (e.g. in this forum). Videos on YouTube, or similar video sites. Helpful links to online information / guides (e.g. Wiki articles, making best use of certain software features).  
      Note: If you wish to post a tutorial / share some personal advice, it's preferred to do so inside a new topic here in the modding forum. This way it keeps discussion contained inside individual topics, making each subject easier to follow, and allows the author to respond to comments.

      I'll update links accordingly in the list below:
       

      Modding General Info   (9 resources)

      (Info)  Modding API – Paradox Interactive
      (Info)  Unity Scripting API – Unity Technologies
      (Info)  Unity Manual – Unity Technologies
      (Info)  Location of User Data stored for the game – Paradox Interactive
      (Info)  Modding Properties Info Compendium – TPB / Skylines Info Squad
      (Video)  How to Install Cities: Skylines Mods – CapTon
      (Video)  How to Download Cities Skylines Mods on MAC – YouTube
      (Video)  Developer Stream: Mod Masterclass – HenkkaArt
      (Tutorial)  Unity Tutorials – Unity Technologies

      Modding   (7 resources)
      (Tutorial) Modding Tutorial 0: Your first mod - boformer
      (Tutorial) Modding Tutorial 1: Prop remover - boformer
      (Tutorial) Modding Tutorial 2: Road Tree replacer - boformer
      (Tutorial) Modding Tutorial 3: Show Limits - boformer
      (Tutorial) Modding Tutorial 4: Make historical - boformer
      (Tutorial)  How to create a Building Theme Mod – Donnerwurm
      (Tutorial)  Manipulating networks with a Hex Editor – Sims firehouse

      3D Modelling   (13 resources)

      (Info)  Polygon Count (Wiki article) – polycount
      (Info)  Free 3D Software and Resources for your Modelling Needs – Mr_Maison
      (Info)  An important reminder about Normal Maps – Cool_Z
      (Video)  Create an Asset for Cities: Skylines from Scratch – BigMasterBig
      (Video)  Making a Custom Model for Cities Skylines – YouTube
      (Video)  Import Model from Blender to Cities: Skylines – YouTube
      (Video)  Blender: Creating low poly Stones and Rocks – YouTube
      (Video)  Importing Sketchup Models – YouTube
      (Video)  Basic Building UV Mapping with 3ds Max 2008 – YouTube
      (Video)  3ds Max 2010 Tutorials (22 video playlist) – YouTube
      (Video)  Creation of a LOD model in 3ds Max – Shroomblaze
      (Video) Lowpoly stones and rocks (Blender) - YouTube
      (Tutorial)  How to turn a Model into Game Ready Asset (3ds Max) – Shroomblaze

      Asset Texturing   (12 resources)
      (Info)  NVIDIA Texture Tools for Adobe Photoshop – NVIDIA Corporation
      (Video) LOD Texture Baking (3DS Max) - Ronyx
      (Video) LOD Texture Baking (Blender) - Jerenable
      (Tutorial)  Bake Normal Maps for realistic, low-poly rocks, bushes and trees (and anything else) – boformer
      (Tutorial)  Illumination Map Texturing Overview – GC_Vos
      (Tutorial)  Detailed Nightlights – Cool_Z
      (Tutorial)  Common Nightlight mistakes you should avoid – Cool_Z
      (Tutorial)  Train Illumination.....a Different Animal – Mr_Maison
      (Tutorial)  How to use Alphas for grilles or balconies – Cool_Z
      (Tutorial)  Floors for Reflective Buildings... and others too – Cool_Z
      (Tutorial)  Speculars without artefacts – Cool_Z
      (Tutorial)  Map Editor: How to recreate roads from the real world – Krozjin

      Assets   (12 resources)
      (Tutorial)  Asset Creation 101 – Shroomblaze & various collaborators
      (Tutorial)  Creating & Applying Custom Props – TallgeeseIV
      (Tutorial)  How to add Custom Menu Icons to (almost) any Asset - The easier way – TPB
      (Tutorial)  How to add custom Content Manager Thumbnails to assets – TPB
      (Tutorial)  How to Place Assets Anywhere – TPB
      (Tutorial)  Remove Dirt from Tree and Prop Assets – TPB
      (Tutorial)  How to Change an Assets Category Tab while still retaining service info of another Category – Shroomblaze
      (Tutorial)  How to make a Blinking Neon Sign – boformer
      (Tutorial)  How to add lights to a prop – boformer
      (Tutorial)  How to create your own (complete) Building Style – boformer
      (Tutorial) Tutorial for Decal Creators - boformer
      (Video) Big Decals - Ronyx

      Custom Content / Workshop   (4 resources)
      (Info)  Steam Workshop "Who to follow?" Suggestions – TPB / Skylines Info Squad
      (Info)  Content creator rights and how to enforce them - Avanya
      (Tutorial)  Sub Buildings Enabler Documentation for Asset Creators – boformer
      (Tutorial)  How to update your Workshop Assets - The easy way – TPB
       
      (57 total resources)
       
      Note:
      See this page for a list of Workshop modding guides / info, which may not already be listed above.
       
    • By boformer
      In this tutorial, we will replicate the functionality of the "Make Historical" checkbox in SimCity 4 (stop building from leveling up). The mod will utilize the same interfaces as the Control Building Level Up mod. You will learn how to inspect and modify user interfaces with ModTools, how the info panels of the game work, how to add a checkbox to the UI, how to control building level up with the official API, how to get notified when a building is added/removed and how to serialize custom data in the savegame. Once again, we will use the loading and threading hooks provided by the API.

       
      Step 1: Project Setup
      Create a new project named "MakeHistorical" 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 MakeHistorical { public class MakeHistoricalMod : IUserMod { public string Name => "Make Historical"; public string Description => "Prevents the level up of buildings"; } }  
      Step 2: Data Storage with Serialization Hook
      Data Structure
      We will store the ids (ushort) of the historical building instances in the savegame. Like in Tutorial 2 Step 4, we will create a data class that holds our data:
      using System.Collections.Generic; namespace MakeHistorical { public class MakeHistoricalData { public List<ushort> HistoricalBuildingIds { get; set; } = new List<ushort>(); } } The game requires additional savegame data to be in byte[] (byte array) format (basically zeros and ones, very low level). Luckily, Colossal Order provides a few useful tools to convert data classes to byte arrays (package ColossalFramework.IO). To use these tools, our data class must implement the IDataContainer interface.
      Extend the class like this:
      using System.Collections.Generic; using System.Linq; using ColossalFramework.IO; namespace MakeHistorical { public class MakeHistoricalData : IDataContainer { // The key for our data in the savegame public const string DataId = "MakeHistorical"; // Version of data save format // This is important when you add new fields to MakeHistoricalData public const int DataVersion = 0; public List<ushort> HistoricalBuildingIds { get; set; } = new List<ushort>(); // This serializes the object (to bytes) public void Serialize(DataSerializer s) { // convert ushort list to int array int[] ids = HistoricalBuildingIds.Select(id => (int)id).ToArray(); s.WriteInt32Array(ids); } // This reads the object (from bytes) public void Deserialize(DataSerializer s) { int[] ids = s.ReadInt32Array(); // convert int array to ushort list HistoricalBuildingIds = ids.Select(id => (ushort)id).ToList(); } // Validates that all building ids are active public void AfterDeserialize(DataSerializer s) { if (!BuildingManager.exists) return; List<ushort> validatedBuildingIds = new List<ushort>(); Building[] buildingInstances = BuildingManager.instance.m_buildings.m_buffer; // itertate through all building ids, filter active ids foreach (ushort buildingId in HistoricalBuildingIds) { if (buildingInstances[buildingId].m_flags != Building.Flags.None) { validatedBuildingIds.Add(buildingId); } } HistoricalBuildingIds = validatedBuildingIds; } } } What happened?
      We added the constants DataId and DataVersion. We will use these in the next step We implemented the Serialize method: In this method, we convert our list of ushort numbers to an integer array (the serializer does not support ushort) and pass it to a DataSerializer, which converts our array to bytes. We implemented the Deserialize method, which does the opposite of Serialize We implemented AfterDeserialize, which validates that all deserialized building ids belong to buildings (important when the mod was disabled for some time, to keep the data clean) Serialization System
      Colossal Order provides a serialization hook for mods (ISerializableDataExtension/SerializableDataExtensionBase). This hook is invoked when the savegame data is loaded, and when the user presses the save button. The game also provides a storage interface (ISerializableData) that allows us to write byte arrays to savegame and read byte arrays from savegame.
      Combined with the data class and the byte array conversion tools provided by Colossal Order, we will use this hook to save our data. Add a new MakeHistoricalDataManager class:
      using ColossalFramework.IO; using ICities; using System.IO; using UnityEngine; namespace MakeHistorical { public class MakeHistoricalDataManager : SerializableDataExtensionBase { // The data object of our mod private MakeHistoricalData _data; public override void OnLoadData() { // Get bytes from savegame byte[] bytes = serializableDataManager.LoadData(MakeHistoricalData.DataId); if (bytes != null) { // Convert the bytes to MakeHistoricalData object using (var stream = new MemoryStream(bytes)) { _data = DataSerializer.Deserialize<MakeHistoricalData>(stream, DataSerializer.Mode.Memory); } Debug.LogFormat("Data loaded (Size in bytes: {0})", bytes.Length); } else { _data = new MakeHistoricalData(); Debug.Log("Data created"); } } public override void OnSaveData() { byte[] bytes; // Convert the MakeHistoricalData object to bytes using (var stream = new MemoryStream()) { DataSerializer.Serialize(stream, DataSerializer.Mode.Memory, MakeHistoricalData.DataVersion, _data); bytes = stream.ToArray(); } // Save bytes in savegame serializableDataManager.SaveData(MakeHistoricalData.DataId, bytes); Debug.LogFormat("Data saved (Size in bytes: {0})", bytes.Length); } } } What happens here?
      The _data property stores our mod data The OnLoadData method attempts to read the saved byte array from the savegame. If the array was found, it converts the bytes to a MakeHistoricalData object (our data structure). If no array was found, it creates a new MakeHistoricalData object (new city or mod enabled for the first time). The OnSaveData method converts the MakeHistoricalData object to bytes, then saves the bytes in the savegame. Tip: To understand how data versioning works, look at this example data manager.
      Singleton
      To make the data readable from other classes, we will make MakeHistoricalDataManager implement a simple singleton pattern. We willl also add a few methods to lookup, add and remove building ids:
      // Singleton getter public static MakeHistoricalDataManager Instance { get; private set; } public bool IsHistorical(ushort buildingId) { return _data.HistoricalBuildingIds.Contains(buildingId); } public void AddBuildingId(ushort buildingId) { if (_data.HistoricalBuildingIds.Contains(buildingId)) return; _data.HistoricalBuildingIds.Add(buildingId); Debug.Log($"Historical Building {buildingId} added"); } public void RemoveBuildingId(ushort buildingId) { if (!_data.HistoricalBuildingIds.Contains(buildingId)) return; _data.HistoricalBuildingIds.Remove(buildingId); Debug.Log($"Historical Building {buildingId} removed"); } public override void OnCreated(ISerializableData serializedData) { base.OnCreated(serializedData); Instance = this; // initialize singleton } public override void OnReleased() { Instance = null; // reset singleton } Now we can use a simple statement to check if a building is historical:
      bool h = MakeHistoricalDataManager.Instance.IsHistorical(1234); Final code of the MakeHistoricalDataManager class.
       
      Step 3: Removing destroyed buildings from the list
      We have to remove buildings from the list when they are buldozed. Luckily CO added a hook just for that (not documented in the wiki):
      using ICities; using UnityEngine; namespace MakeHistorical { public class MakeHistoricalBuildingMonitor : BuildingExtensionBase { public override void OnBuildingReleased(ushort id) { if (MakeHistoricalDataManager.Instance.IsHistorical(id)) { // Remove demolished/destroyed buildings from the list of historical buildings MakeHistoricalDataManager.Instance.RemoveBuildingId(id); } } } } Tip: With the IBuildingExtension/BuildingExtensionBase hook, you can also manipulate which buildings are spawned, and there are hooks for building creation and relocation.
       
      Step 4: Inspecting And Manipulating User Interfaces with ModTools and ILSpy
      Like in SimCity 4, we will add a "Make Historical" checkbox to the building info window:

      To do that, we have to find out how to access the info window programmatically.
      ModTools supports us with its "debug view". You can toggle it with CTRL + R. Then move your mouse over the info panel (so that the tint of the panel changes to green) and press CTRL + F:

      The UI window component is now displayed in the ModTools scene explorer. The class name of the element is ZonedBuildingWorldInfoPanel.
      In the scene explorer, we can change properties like the colour, size and position of the window. We can also look at the child UI elements (buttons, text labels). The position of the child elements is determined by the relativeLayout property.
      Noted values:
      Height: 321 Left Padding: 14 (based on the child element positions) Bottom Padding: 27 (based on the child element positions) Now that we know the class name, we can locate the class in ILSpy to find out how to access it programmatically:

      ZonedBuildingWorldInfoPanel is a subclass of WorldInfoPanel, which contains a static Show<>() method:
      // WorldInfoPanel (taken from ILSpy) public static void Show<TPanel>(Vector3 worldMousePosition, InstanceID instanceID) where TPanel : WorldInfoPanel { TPanel tPanel = UIView.library.Show<TPanel>(typeof(TPanel).Name, false); tPanel.SetTarget(worldMousePosition, instanceID); tPanel.component.opacity = 1f; } After further investigation (use ILSpy's "Analyze" feature), you will find a Get<>() method in UIView.library which returns the ZonedBuildingWorldInfoPanel instance:
      var panel = UIView.library.Get<ZonedBuildingWorldInfoPanel>(typeof(ZonedBuildingWorldInfoPanel).Name); Note: There is only one panel instance, which is just filled with the data of the currently selected building. The panel even exists when no building is selected, it's just hidden.
       
      Step 5: Adding a Checkbox to the Info Panel
      Finding the right property values for user interfaces is a finicky task, especially when you have to restart the game after every little change. Using the ModTools console, we can add and modify our checkbox while the game is running. Run this script in the ModTools console (F7):
      var panel = UIView.library.Get<ZonedBuildingWorldInfoPanel>(typeof(ZonedBuildingWorldInfoPanel).Name); var checkBox = panel.component.AddUIComponent<UICheckBox>(); checkBox.width = panel.component.width; checkBox.height = 20f; checkBox.clipChildren = true; UISprite sprite = checkBox.AddUIComponent<UISprite>(); sprite.spriteName = "ToggleBase"; sprite.size = new Vector2(16f, 16f); sprite.relativePosition = Vector3.zero; checkBox.checkedBoxObject = sprite.AddUIComponent<UISprite>(); ((UISprite)checkBox.checkedBoxObject).spriteName = "ToggleBaseFocused"; checkBox.checkedBoxObject.size = new Vector2(16f, 16f); checkBox.checkedBoxObject.relativePosition = Vector3.zero; checkBox.label = checkBox.AddUIComponent<UILabel>(); checkBox.label.text = " "; checkBox.label.textScale = 0.9f; checkBox.label.relativePosition = new Vector3(22f, 2f); checkBox.name = "MakeHistorical"; checkBox.text = "Make Historical"; This will add everything that is required for a checkbox, giving it the name "MakeHistorical". It is still in the wrong position:

      Tip: SamsamTS created a very useful utility class that allows you to create various UI elements. The code above was copied from that class.
       
      With ModTools, we can experiment with the position without restarting the game. Execute this code in the ModTools console:
      var panel = UIView.library.Get<ZonedBuildingWorldInfoPanel>(typeof(ZonedBuildingWorldInfoPanel).Name); var checkBox = panel.component.Find<UICheckBox>("MakeHistorical"); checkBox.relativePosition = new Vector3(14f, 164f + 130f + 5f); Result:

       
      Now we just have to increase the height of the window a little bit. Execute this code in the ModTools console:
      var panel = UIView.library.Get<ZonedBuildingWorldInfoPanel>(typeof(ZonedBuildingWorldInfoPanel).Name); panel.component.height = 321f + 5f + 20f; Result:

       
      Now, we will put the code we just executed with the console into the loading hook of our mod:
      using ColossalFramework.UI; using ICities; using UnityEngine; namespace MakeHistorical { public class MakeHistoricalLoading : LoadingExtensionBase { private UICheckBox _makeHistoricalCheckBox; public override void OnLevelLoaded(LoadMode mode) { if (_makeHistoricalCheckBox != null) return; ZonedBuildingWorldInfoPanel panel = UIView.library.Get<ZonedBuildingWorldInfoPanel>(typeof(ZonedBuildingWorldInfoPanel).Name); UICheckBox checkBox = panel.component.AddUIComponent<UICheckBox>(); checkBox.width = panel.component.width; checkBox.height = 20f; checkBox.clipChildren = true; UISprite sprite = checkBox.AddUIComponent<UISprite>(); sprite.spriteName = "ToggleBase"; sprite.size = new Vector2(16f, 16f); sprite.relativePosition = Vector3.zero; checkBox.checkedBoxObject = sprite.AddUIComponent<UISprite>(); ((UISprite)checkBox.checkedBoxObject).spriteName = "ToggleBaseFocused"; checkBox.checkedBoxObject.size = new Vector2(16f, 16f); checkBox.checkedBoxObject.relativePosition = Vector3.zero; checkBox.label = checkBox.AddUIComponent<UILabel>(); checkBox.label.text = " "; checkBox.label.textScale = 0.9f; checkBox.label.relativePosition = new Vector3(22f, 2f); checkBox.name = "MakeHistorical"; checkBox.text = "Make Historical"; checkBox.relativePosition = new Vector3(14f, 164f + 130f + 5f); panel.component.height = 321f + 5f + 16f; _makeHistoricalCheckBox = checkBox; } } }
      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, open the building info window for a growable building. Et voila:

       
      Step 6: Processing Checkbox Events
      To process checkbox events, we will add a delegate to the CheckedChange event:
      public override void OnLevelLoaded(LoadMode mode) { ... checkBox.eventCheckChanged += (component, check) => { ushort buildingId = WorldInfoPanel.GetCurrentInstanceID().Building; if (check) { MakeHistoricalDataManager.Instance.AddBuildingId(buildingId); } else { MakeHistoricalDataManager.Instance.RemoveBuildingId(buildingId); } }; } Clicking the checkbox now triggers the data manager:

       
      Step 7: Monitoring the ZonedBuildingWorldInfoPanel with a Threading Hook
      There is another problem: The checkbox is not updated when you select a building. We will use a threading hook to update the checkbox:
      using ColossalFramework.UI; using ICities; namespace MakeHistorical { public class MakeHistoricalPanelMonitor : ThreadingExtensionBase { private ZonedBuildingWorldInfoPanel _panel; private UICheckBox _makeHistoricalCheckBox; private ushort _lastBuildingId = 0; // called every frame public override void OnUpdate(float realTimeDelta, float simulationTimeDelta) { if (!FindComponents()) return; if (_panel.component.isVisible) { ushort buildingId = WorldInfoPanel.GetCurrentInstanceID().Building; if (_lastBuildingId != buildingId) { // display the right checkbox state _makeHistoricalCheckBox.isChecked = MakeHistoricalDataManager.Instance.IsHistorical(buildingId); _lastBuildingId = buildingId; } } else { _lastBuildingId = 0; } } private bool FindComponents() { if (_panel != null && _makeHistoricalCheckBox != null) return true; _panel = UIView.library.Get<ZonedBuildingWorldInfoPanel>(typeof(ZonedBuildingWorldInfoPanel).Name); if (_panel == null) return false; _makeHistoricalCheckBox = _panel.component.Find<UICheckBox>("MakeHistorical"); return _makeHistoricalCheckBox != null; } } }  
      Step 8: Controlling Building Level Up
      It took quite some time to get to this point (UI and serialization are always very time consuming). The last missing piece is the ILevelUpExtension/LevelUpExtensionBase hook, which monitors and manipulates the level up behaviour of buildings. Probably the least complicated part of the mod:
      using ICities; namespace MakeHistorical { public class MakeHistoricalLevelUpMonitor : LevelUpExtensionBase { public override ResidentialLevelUp OnCalculateResidentialLevelUp(ResidentialLevelUp levelUp, int averageEducation, int landValue, ushort buildingID, Service service, SubService subService, Level currentLevel) { if (MakeHistoricalDataManager.Instance.IsHistorical(buildingID)) { levelUp.targetLevel = currentLevel; } return levelUp; } public override CommercialLevelUp OnCalculateCommercialLevelUp(CommercialLevelUp levelUp, int averageWealth, int landValue, ushort buildingID, Service service, SubService subService, Level currentLevel) { if (MakeHistoricalDataManager.Instance.IsHistorical(buildingID)) { levelUp.targetLevel = currentLevel; } return levelUp; } public override IndustrialLevelUp OnCalculateIndustrialLevelUp(IndustrialLevelUp levelUp, int averageEducation, int serviceScore, ushort buildingID, Service service, SubService subService, Level currentLevel) { if (MakeHistoricalDataManager.Instance.IsHistorical(buildingID)) { levelUp.targetLevel = currentLevel; } return levelUp; } public override OfficeLevelUp OnCalculateOfficeLevelUp(OfficeLevelUp levelUp, int averageEducation, int serviceScore, ushort buildingID, Service service, SubService subService, Level currentLevel) { if (MakeHistoricalDataManager.Instance.IsHistorical(buildingID)) { levelUp.targetLevel = currentLevel; } return levelUp; } } } The logic is very simple: When the building is historical, the target level is set to the current building level.
      And we are done! Buildings with the "Make Historical" checkbox enabled will no longer level up. The list of historical buildings is stored in the savegame.

      Happy Coding!
      Download Source
    • By boformer
      Right now I only have little time, and I'm not able to write new mods. But I want to share my knowlege about Cities: Skylines mod development with you!
      Mod Development Tutorials
       
    • By boformer
      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:

      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:

      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":

      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:

      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:

       
      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; } } } }
      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:

      Happy Coding!
      Download Source
       
      Next Part:
       
  • Newest STEX Uploads

  • Recent STEX Reviews

  • Recent STEX Comments