Maintain mods automatically

From Eco - English Wiki

Template:ModdingOutdated > Version: 7.4.6

> Difficulty: Medium

Introduction[edit | edit source]

Hello and welcome, my name is Norca. Every time a new game version comes out, the users are happy about new features and balance changes. But creator of mods are living in fear and trouble about bigger or smaller changes, hopping there mods are not broken. Certainly in case there mods are touching vanilla files (vanilla files are files from the original game, untouched by mods). To deal with this problem I wrote a small C# class how scan the file folder and write our specific changes back into the files.

My Specific Problem[edit | edit source]

My Mod adds a new kind of fuel item, that´s used by certain vanilla objects. For this I have to change the fuelTypeList variable inside the .cs file for all the objects, I want to run with my new fuel. I wanna change this:

private static Type[] fuelTypeList = new Type[]
{
	typeof(TallowItem),
	typeof(OilItem),
};

to this:

private static Type[] fuelTypeList = new Type[]
{
	typeof(TallowItem),
	typeof(OilItem),
	typeof(WaxItem),
};

That's a problem. How should I maintain my mod in case strangeloop changes this files?

  • Ship the overwritten files with your mod? Whats about interaction with other mods (how writes first?)? Or the case you want files removed from game?
  • Write an explanation for the mod user HowTo changes this files by hand? So he redo all your mod specific work. I hope he makes no mistakes with this task. (Proprablably a NoGo for some users)

Both solutions work and have there own up and downside. Modding in Eco gives us access to C# files (.cs), this is a dream! With this we can do nearly everything (good or bad, it depends on you). Load external libarys, read and write into files or access to webserver. So my solution is, using the mod API to solve this by code! My class checks the files inside the Mods folder I wan't to change and replaces the text of the .cs files directly how I need. This reduce the amount of extra work for the user and add the changes I really want.

Full Solution[edit | edit source]

Here is the full code I use

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Eco.Gameplay.Players;
using Eco.Shared.Localization;
namespace MyModNamespace
{
    using System;
    using Eco.Core.Plugins.Interfaces;
    using Eco.Core.Utils;
    using Eco.Gameplay.Systems.Chat;
    using System.IO;

    public class MyMod : IModKitPlugin, IInitializablePlugin
    {
        public static string ModVersion = "0.9";
        public string GetStatus()
        {
            return String.Empty;
        }

        public void Initialize(TimedTask timer)
        {
            ModRegister modRegister = new ModRegister();
            if (modRegister.restartRequired)
            {
                // Inform user about files changing
                string restartText = " Files have changed, server restart required";
            }

            ChatManager.ServerMessageToAllLoc("Mod version: " + ModVersion + " is loaded!" + restartText, false);
        }
    }

     public class ModRegister
     {
        public bool restartRequired = false;

        private List<string> targetFiles = new List<string> {
            "CandleStand.cs",
            "CeilingCandle.cs",
            "TallowCandle.cs",
            "TallowLamp.cs",
            "TallowWallLamp.cs",
            "WallCandle.cs"
        };
        private string TextToAdd = "typeof(WaxItem),";
        private string TextBeforeAdd = "typeof(OilItem),";

        public ModRegister()
        {
            string modFolderPath = Directory.GetCurrentDirectory() + "/Mods/";  // GetCurrentDirectory returns the "Server" folder
            string[] allfilesNames = Directory.GetFiles(modFolderPath, "*.*", SearchOption.AllDirectories);
            foreach (var fileName in allfilesNames)
            {
                FileInfo fileInfo = new FileInfo(fileName);
                if (targetFiles.Contains(fileInfo.Name))
                {
                    AddWaxItemToFuelList(fileInfo);
                }
            }
        }

        private void AddWaxItemToFuelList(FileInfo fileInfo)
        {
            string fileContent = File.ReadAllText(fileInfo.FullName);
            if (fileContent.Contains(TextToAdd))
                return; // Item is already added, abort

            int index = fileContent.IndexOf(TextBeforeAdd);
            if (index > 0)
            {
                // Write text to .cs file and save it
                fileContent = fileContent.Insert(index + TextBeforeAdd.Length, TextToAdd);
                File.WriteAllText(fileInfo.FullName, fileContent);
                // We touched a file, Server needs to restart to detect changes
                restartRequired = true;
            }
        }
    }
}

The class MyMod is the entry point for our mod and something like the [the hearth of our mod](https://github.com/StrangeLoopGames/EcoModKit/wiki/Reacting-To-Player-Events#getting-started). Every user logged to the server will create on instance of this class. The Initialize() function then will create a instance of our ModRegister class. Lets take a deeper look into this.

Scan for files to overwrite[edit | edit source]

public ModRegister()
{
    string modFolderPath = Directory.GetCurrentDirectory() + "/Mods/";  // GetCurrentDirectory returns the "Server" folder
    string[] allfilesNames = Directory.GetFiles(modFolderPath, "*.*", SearchOption.AllDirectories);
    foreach (var fileName in allfilesNames)
    {
        FileInfo fileInfo = new FileInfo(fileName);
        if (targetFiles.Contains(fileInfo.Name))
        {
           AddWaxItemToFuelList(fileInfo);
        }
    }
}

Here we ask the Filesystem for all files inside the "/Server/Mods" folder. This can be textfiles, .cs files or .unityfiles. We check if the names is one of our target files and call a function for this file.

Overwrite vanilla files[edit | edit source]

private void AddWaxItemToFuelList(FileInfo fileInfo)
{
	string fileContent = File.ReadAllText(fileInfo.FullName);
	if (fileContent.Contains(TextToAdd))
		return; // Item is already added, abort

	int index = fileContent.IndexOf(TextBeforeAdd);
	if (index > 0)
	{
		// Write text to .cs file and save it
		fileContent = fileContent.Insert(index + TextBeforeAdd.Length, TextToAdd);
		File.WriteAllText(fileInfo.FullName, fileContent);
		// We touched a file, Server needs to restart to detect changes
		restartRequired = true;
	}
}

Add first read the full file from hard drive. Then we check if the file content already contains the text, we want to add. After this, I search for the specific point I want to add my Text and add my text after this. At last I save the changes to the file.

Easy Peasy?!? And whats now?[edit | edit source]

For my small problem this works very well. Changes are made directly inside the .cs files and they are automatically corrected in case something changes them. The solution is easily modifiable for other kinds of use cases and can even make multiple different changes in the same file or delete files. Its just Text Replacement. Because the mods are compiled at server startup, we have to restart the server to see the changes. Luckily for me, my server auto restarts everyday. An other idea is to write a .bat file with the same effect. This can be started even for offline server (I preferer the c# way). Anyway, you should add try catch mechanic to avoid bigger issues or crashes with our mod. This maybe helps other mod creators to maintain there mods in a easy way. Have fun with Eco. You can find me on Discord Norca#3305