Modding:Moddable Mod

From Vintage Story Wiki
Revision as of 19:33, 5 September 2020 by Codemeister32 (talk | contribs)

- THIS IS CURRENTLY A DRAFT -

This article requires a setup development environment. If you don't have one, read the tutorial at 'Setting up your Development Environment'. For brevity, this article presupposes some familiarity with VintageStory code modding, primarily how to package a mod (https://wiki.vintagestory.at/index.php?title=Modding:Mod_Packaging) and familiarity with what a ModSystem is. If you are looking to create your first mod, we recommend starting here =Link to first code mod sample=.

Introduction

VintageStory makes all loaded mods available through the API. This makes it possible for code mods to interact with eachother and access eachother's public properties.

The loading and retrieving of mods is done through the =ModLoader=, which is accessible via the API. We can retrieve both the full =Mod= with all it's useful data, as well as it's contained ModSystems.

This is superbly useful in multiple scenarios, for example -

  • Splitting up a large mod into smaller logical parts that communicate - a modpack.
  • Extendable mods, for example - the creative tool for growing trees, which lets you register new tree types.

In this article, we will be creating two mods. Our first mod will provide a method for our second mod to interact with. We will demonstrate the use of the =ModLoader= and also explain how to reference an external mod and have that reference resolve at game runtime. We'll also take note of execution order to make sure our moddable mod is loaded by the ModLoader before we try to access it.

Note that, excluding the use of reflection or the use of an intermediary library, which is outside the scope of this article, only compiled mods will be able to resolve eachother's references.

TipMod & SpawnMod

The two mods we will be creating are named "TipMod" and "SpawnMod".

"TipMod" will store useful gameplay tips and periodically print them in chat. It will also provide a method which other mods can use to add their own tips.

"SpawnMod" will register a command which teleports the player to his set or default spawnpoint. It will also access "TipMod" and add it's own tips about the command.

Preperation

We start by setting up two empty mods, "TipMod" and "SpawnMod". In each mod, we add a new *.cs source file - we will be naming these files TipMod.cs and SpawnMod.cs respectively. In our .cs files, we do the following.

  • Add the necessary using directives.
  • Create a class named after the mod it's contained it.
  • Have our classes inherit ModSystem - the base for any VintageStory code mod.
  • Override the StartServerSide method to access the API.
  • Make the TipMod class public so that it's accessible by other mods.

- img of folders -

// In TipMod.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Vintagestory.API.Common;
using Vintagestory.API.Server;

namespace tipmod.src
{
    public class TipMod : ModSystem
    {
        public override void StartServerSide(ICoreServerAPI api)
        {
            base.StartServerSide(api);
        }
    }
}
// In SpawnMod.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Vintagestory.API.Common;
using Vintagestory.API.Server;

namespace spawnmod.src
{
    class SpawnMod : ModSystem
    {
        public override void StartServerSide(ICoreServerAPI api)
        {
            base.StartServerSide(api);
        }
    }
}

This concludes setting up the basis of our mods. Lastly, in TipMod, we create an additional file called Tip.cs, where we will define the data structure for tips.

// In Tip.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace tipmod.src
{
    public class Tip
    {
        string author;
        string tip;

        public Tip(string author, string tip)
        {
            this.author = author;
            this.tip = tip;
        }

        public override string ToString() {
            return "Tip by \"" + author + "\": " + tip; 
        }
    }
}

We are now ready to start adding the functionality of our mods. We will start with TipMod, as that will be our point of interaction.

Setting up TipMod

For TipMod, we do the following.

  • Store the API object in a variable so that we can access it throughout our class.
  • Define a List that stores tips.
  • Create a method that selects a random tip and prints it in chat for all players, printing a default message if there are no existing tips.
  • Register that method to be called at set intervals by the timer, which is available in the event API.
  • Define a public method for adding new Tips.
    // In TipMod.cs
    public class TipMod : ModSystem
    {
        ICoreServerAPI api;

        List<Tip> tips = new List<Tip>();
        double tipInterval = 10;
        public override void StartServerSide(ICoreServerAPI api)
        {
            base.StartServerSide(api);
            this.api = api;

            api.Event.Timer(OnInterval, tipInterval);
        }

        private void OnInterval()
        {
            int tipCount = tips.Count();
            string message = "There aren't any listed tips";

            if (tipCount > 0)
            {
                // Select a random number in the range of [0-1]
                double random = api.World.Rand.NextDouble();
                // Make the number relative to the size of our tips list
                int randomTip = (int)Math.Floor(random * tipCount);

                Tip tip = tips[randomTip];

                message = tip.ToString();
            } 


            api.SendMessageToGroup(GlobalConstants.GeneralChatGroup, message, EnumChatType.AllGroups);
        }

        public void AddTip(Tip tip)
        {
            tips.Add(tip);
        }
    }

For testing purposes, we set the interval to be very short (10 seconds). Feel free to change this accordingly.

Remember, both classes and methods which other mods will interact with have to be declared public. In C#, classes and methods are not public by default. You can read more about this here (https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/access-modifiers)

We can now compile our mod, add it to our VintageStory mod folder and test it ingame. If the there are occasional chat messages claiming that no tips are listed, our mod is working. We are ready to move on to the next step - setting up SpawnMod and then having it interact with TipMod.

Setting up SpawnMod

Let's first setup our mods functionality, We register a command which teleports the player to spawn. Conveniently the Player object holds it's entity, which defines a method for teleporting itself to a location.

    class SpawnMod : ModSystem
    {
        public override void StartServerSide(ICoreServerAPI api)
        {
            base.StartServerSide(api);

            api.RegisterCommand("spawn", "Teleport to spawn", "", OnCmdSpawn);
        }

        private void OnCmdSpawn(IServerPlayer player, int groupId, CmdArgs args)
        {
            player.Entity.TeleportTo(player.SpawnPosition);
        }
    }

SpawnMod is ready, let's test it. The command /spawn should now teleport us to spawn. We've setup the base of our mods, now let's make SpawnMod interact with TipMod.

Mods interacting

Before we get started, we have to add a reference in SpawnMod to TipMod. If you are using Visual Studio, in your Solution Explorer, directly under your project, right-click "References" and select "Add Reference".

- img -

Here we locate either the TipMod project or the compiled .dll file and add it. After adding the reference, make sure it is not copying the file to the output folder when compiling - having multiple .dll files with ModSystems in them will break your mod.

- img -

Now we're ready to have SpawnMod add tips. Let's go ahead and do the following.

  • Add a using directive for tipmod.src.
  • Retrieve TipMod through the ModLoader by passing it's type.
  • Call the method provided by TipMod and add a variety of tips.
        public override void StartServerSide(ICoreServerAPI api)
        {
            base.StartServerSide(api);

            api.RegisterCommand("spawn", "Teleport to spawn", "", OnCmdSpawn);

            using tipmod.src;
            TipMod tipMod = api.ModLoader.GetModSystem<TipMod>();
            tipMod.AddTip(new Tip("codemeister32", "To quickly return to spawn, type /spawn"));
            tipMod.AddTip(new Tip("codemeister32", "Can't find your way home? Type /spawn"));
            tipMod.AddTip(new Tip("codemeister32", "Being chased by wolves? Quick, type /spawn"));
        }

Conclusion

Following the steps in this article, this is what our resulting code looks like, spread across two mods.

// In TipMod.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Vintagestory.API.Common;
using Vintagestory.API.Config;
using Vintagestory.API.Server;

namespace tipmod.src
{
    public class TipMod : ModSystem
    {
        ICoreServerAPI api;

        List<Tip> tips = new List<Tip>();
        double tipInterval = 10;

        public override void StartServerSide(ICoreServerAPI api)
        {
            base.StartServerSide(api);
            this.api = api;

            api.Event.Timer(OnInterval, tipInterval);
        }

        private void OnInterval()
        {
            int tipCount = tips.Count();
            string message = "There aren't any listed tips";

            if (tipCount > 0)
            {
                // Select a random number in the range of [0-1]
                double random = api.World.Rand.NextDouble();
                // Make the number relative to the size of our tips list
                int randomTip = (int)Math.Floor(random * tipCount);

                Tip tip = tips[randomTip];

                message = tip.ToString();
            } 


            api.SendMessageToGroup(GlobalConstants.GeneralChatGroup, message, EnumChatType.AllGroups);
        }

        public void AddTip(Tip tip)
        {
            tips.Add(tip);
        }
    }
}
// In Tip.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace tipmod.src
{
    public class Tip
    {
        string author;
        string tip;

        public Tip(string author, string tip)
        {
            this.author = author;
            this.tip = tip;
        }

        public override string ToString() {
            return "Tip by \"" + author + "\": " + tip; 
        }
    }
}
// In SpawnMod.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Vintagestory.API.Common;
using Vintagestory.API.Server;
using tipmod.src;

namespace spawnmod.src
{
    class SpawnMod : ModSystem
    {
        public override void StartServerSide(ICoreServerAPI api)
        {
            base.StartServerSide(api);

            api.RegisterCommand("spawn", "Teleport to spawn", "", OnCmdSpawn);

            TipMod tipMod = api.ModLoader.GetModSystem<TipMod>();

            tipMod.AddTip(new Tip("codemeister32", "To quickly return to spawn, type /spawn"));
            tipMod.AddTip(new Tip("codemeister32", "Can't find your way home? Type /spawn"));
            tipMod.AddTip(new Tip("codemeister32", "Being chased by wolves? Quick, type /spawn"));
        }

        private void OnCmdSpawn(IServerPlayer player, int groupId, CmdArgs args)
        {
            player.Entity.TeleportTo(player.SpawnPosition);
        }
    }
}

We're now ready to test our mods to see if they're interacting successfully. After placing both our compiled mods in the mods folder, we should see a random tip pop up in the chat every 10 seconds. If so, well done! You've successfully followed the article and have created a moddable mod.

- img -

Congratulations!

Troubleshooting

If you've ran into problems setting up either mod, check the error logs located at VintagestoryData/Logs in server-main.txt. Possible causes for either mod not working are as follows.

  • It's a .cs mod and not a .dll (compiled) mod.
  • It's missing modinfo.json.
  • Your version of VintageStory is outdated.
  • You have multiple .dll files containing ModSystems in your mod folder.
  • The referenced mod is not present in the mod folder.

Distribution

Here are the mod versions: -Mod Download here-