Modding:Moddable Mod: Difference between revisions

From Vintage Story Wiki
mNo edit summary
mNo edit summary
Line 2: Line 2:
- THIS IS CURRENTLY A DRAFT -
- THIS IS CURRENTLY A DRAFT -


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


== Introduction ==
== Introduction ==


VintageStory makes it possible to retrieve all loaded mods through the API.
VintageStory makes all loaded mods available through the API.
This makes it possible for code mods to interact with eachother and use eachother's interfaces.
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.  
The loading and retrieving of mods is done through the =ModLoader=, which is accessible via the API.  
Line 16: Line 16:


This is superbly useful in multiple scenarios, for example -
This is superbly useful in multiple scenarios, for example -
* Splitting up a large mod into a modpack, where individual mod parts can communicate with eachother, but don't break if parts of the pack are taken out.
* Splitting up a large mod into smaller logical parts that communicate - a modpack.
* Utility mods that centralize some functionality for other mods or provide an interface for some common action, such as the creative mode gui tool for growing trees, wherein you can register new tree types.
* 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 an interface for our second mod to interact with.
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
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
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.
we try to access it.


Note that, excluding the use of refleciton, which is outside the scope of this article,
Note that, excluding the use of reflection or the use of an intermediary library, which is outside the scope of this article,
<b>only compiled mods will be able to interact with eachother</b>. If you do not compile the code of your mods
<b>only compiled mods will be able to resolve eachother's references</b>.
into .dll files, they will not be able to resolve references to eachother at runtime.


== TipMod & SpawnMod ==
== TipMod & SpawnMod ==
Line 36: Line 35:


"SpawnMod" will register a command which teleports the player to his set or default spawnpoint. It will also
"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 tip describing the command.
access "TipMod" and add it's own tips about the command.


== Preperation ==
== Preperation ==
We start by setting up two empty mods, "TipMod" and "SpawnMod".
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 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 add the necessary using statements, create classes named after our mods and have them inherit ModSystem,
In our .cs files, we do the following.
the base for any VintageStory code mod. In both our mods we override the StartServerSide method to access the API. We also make TipMod public, so it's accessible by other mods.
* 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 -
- img of folders -
Line 122: Line 125:
</syntaxhighlight>
</syntaxhighlight>
We are now ready to start adding the functionality of our mods. We will start with TipMod, as that will be our
We are now ready to start adding the functionality of our mods. We will start with TipMod, as that will be our
moddable mod.
point of interaction.


== Setting up TipMod ==
== Setting up TipMod ==


For TipMod, we do the following
For TipMod, we do the following.
* Define a List that stores tips
* Store the API object in a variable so that we can access it throughout our class.
* Register a timed event that prints a random tip in chat
* Define a List that stores tips.
* Define a public method for adding new 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.


<syntaxhighlight lang="c#">
<syntaxhighlight lang="c#">
    // In TipMod.cs
     public class TipMod : ModSystem
     public class TipMod : ModSystem
     {
     {
Line 149: Line 155:
         {
         {
             int tipCount = tips.Count();
             int tipCount = tips.Count();
            string message = "There aren't any listed tips";


             // Stop if there are no tips
             if (tipCount > 0)
            if (tipCount == 0) return;
            {
                // 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);


            // Select a random number in the range of [0-1]
                Tip tip = tips[randomTip];
            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, tip.ToString(), EnumChatType.AllGroups);
 
             api.SendMessageToGroup(GlobalConstants.GeneralChatGroup, message, EnumChatType.AllGroups);
         }
         }


Line 174: Line 184:
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)
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 /commandhere prints out a message, we are ready
We can now compile our mod, add it to our VintageStory mod folder and test it ingame. If the there are occasional chat messages
to setup SpawnMod and have it interact with TipMod.
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 ==
== 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.  
Before we start interacting with TipMod, let's setup SpawnMod. This involves registering a simple command which teleports the player to spawn.
<syntaxhighlight lang="c#">
<syntaxhighlight lang="c#">
     class SpawnMod : ModSystem
     class SpawnMod : ModSystem
Line 195: Line 204:
     }
     }
</syntaxhighlight>
</syntaxhighlight>
SpawnMod is ready to be tested. When added to the mods folder, the command /spawn should now teleport us to spawn.  
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.
We've setup the base of our mods, now let's make SpawnMod interact with TipMod.


== Mods interacting ==
== Mods interacting ==
Before we get started, we have to add a reference to TipMod. You can either reference the project itself,
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".
or the compiled .dll file. 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 -
- img -


Next, we do the following
Now we're ready to have SpawnMod add tips. Let's go ahead and do the following.
* Add a using statement for tipmod.src
* Add a using directive for tipmod.src.
* Retrieve TipMod through the ModLoader by passing it's type
* Retrieve TipMod through the ModLoader by passing it's type.
* Call the method provided by TipMod and add a variety of tips
* Call the method provided by TipMod and add a variety of tips.


<syntaxhighlight lang="c#">
<syntaxhighlight lang="c#">
Line 217: Line 230:
             api.RegisterCommand("spawn", "Teleport to spawn", "", OnCmdSpawn);
             api.RegisterCommand("spawn", "Teleport to spawn", "", OnCmdSpawn);


            using tipmod.src;
             TipMod tipMod = api.ModLoader.GetModSystem<TipMod>();
             TipMod tipMod = api.ModLoader.GetModSystem<TipMod>();
             tipMod.AddTip(new Tip("codemeister32", "To quickly return to spawn, type /spawn"));
             tipMod.AddTip(new Tip("codemeister32", "To quickly return to spawn, type /spawn"));
Line 224: Line 238:
</syntaxhighlight>
</syntaxhighlight>


We're now ready to test our mods. After placing both our compiled mods in the mods folder, we should be seeing a random tip pop up in the chat every 10 seconds. If no tips are showing up, check the log files for any errors. Don't forget that both our mods have to be compiled and present in the mod folder for them to work.
== Conclusion ==
 
Following the steps in this article, this is what our resulting code looks like, spread across two mods.
 
<syntaxhighlight lang="c#">
// 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";


- img -
            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);
        }
    }
}
</syntaxhighlight>
<syntaxhighlight lang="c#">
// 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;
        }
    }
}
</syntaxhighlight>
<syntaxhighlight lang="c#">
// 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>();


Great! Next, we can add some code to make sure SpawnMod runs without TipMod, if the user of our mod
            tipMod.AddTip(new Tip("codemeister32", "To quickly return to spawn, type /spawn"));
has not deemed it necessary to add.
            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);
        }
    }
}
</syntaxhighlight>


== Making SpawnMod not depend on TipMod ==
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.


add throw statement here
- img -


== Conclusion ==
Congratulations!


All done. This is how our code should look.
== Troubleshooting ==


- code -
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 ==
== Distribution ==
Here are the mod versions:
-Mod Download here-

Revision as of 19:33, 5 September 2020

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