Modding:Moddable Mod
This page was last verified for Vintage Story version 1.19.4.
This article requires a setup development environment. If you don't have one, read the tutorial
on setting up your development environment. For brevity, this article presupposes some familiarity with
VintageStory code modding, primarily how mods are packaged and how to setup a compiled mod project.
If you are looking to create your first code mod, we recommend starting here instead.
Introduction
VintageStory makes all loaded mods available through it's expansive API, which you can explore in the API docs. This makes it possible for code mods to interact with each other and access each other's public properties. It does, however, make whatever mod is including another mod dependent on that mod. If the mod being included is not present or disabled, the including mod will not work.
The loading and retrieving of mods is done through the IModLoader
, which we retrieve from ICoreAPI
. 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 GUI tool for growing trees, which lets you register new tree types and easily test them.
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.
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 can interact with eachother.
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.
Preparation
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 in -
TipMod
andSpawnMod
. - Have our classes inherit
ModSystem
- the base for any VintageStory code mod. - Override the
StartServerSide
method to accessICoreServerAPI
. - Make the
TipMod
classpublic
so that it's accessible by other 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.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, called 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;
}
}
}
Our resulting folder structure looks like this.
We are now ready to extend our mods, starting with "TipMod" - our moddable mod.
Setting up TipMod
For "TipMod", we do the following:
- Store the
ICoreServerAPI
object as a field so that we can access it throughout our class. - Define a
List
that storesTip
objects. - Define a method that selects a random
Tip
from theList
and prints it in chat for all players, printing a default message if theList
is empty. - Register that method to be called at set intervals by the
Timer
, which is available in theIServerEventAPI
. - Define a
public
method for adding newTip
objects.
// 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.
We can now compile our mod, add it to our VintageStory mod folder and test it ingame. If the there are occasional chat messages saying "There aren't any listed tips", 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 IServerPlayer
object stores it's spawn position, as well as the IPlayerEntity
, which defines a method for teleporting itself to a location.
// In SpawnMod.cs
class SpawnMod : ModSystem
{
public override void StartServerSide(ICoreServerAPI api)
{
base.StartServerSide(api);
api.ChatCommands.Create("spawn")
.WithDescription("Teleport to spawn")
.RequiresPlayer()
.RequiresPrivilege(Privilege.chat)
.HandleWith(OnCmdSpawn);
}
private TextCommandResult OnCmdSpawn(TextCommandCallingArgs args)
{
//Spawn position is stored on the server - Our command is called on the server so we can turn our IPlayer into IServerPlayer.
IServerPlayer player = args.Caller.Player as IServerPlayer;
player.Entity.TeleportTo(player.GetSpawnPosition(false));
return TextCommandResult.Success();
}
}
"SpawnMod" is ready. Let's test it. The command /spawn
should now teleport us to spawn.
If our mods are working, we're ready to make "SpawnMod" interact with "TipMod".
Mods interacting
Before we get started, we have to add a reference in "SpawnMod" to "TipMod".
Adding a reference to an external mod
If you are using Visual Studio, in your Solution Explorer, directly under your project, right-click "References" and select "Add Reference".
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.
Accessing the ModLoader
Now we're ready to have "SpawnMod" add tips. Let's go ahead and do the following.
- Add a
using
directive for the namespacetipmod.src
. - Retrieve
TipMod
through theModLoader
by passing it's type. - Call the
AddTip
method provided byTipMod
and add a variety of tips.
public override void StartServerSide(ICoreServerAPI api)
{
base.StartServerSide(api);
api.ChatCommands.Create("spawn")
.WithDescription("Teleport to spawn")
.RequiresPlayer()
.RequiresPrivilege(Privilege.chat)
.HandleWith(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.ChatCommands.Create("spawn")
.WithDescription("Teleport to spawn")
.RequiresPlayer()
.RequiresPrivilege(Privilege.chat)
.HandleWith(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 TextCommandResult OnCmdSpawn(TextCommandCallingArgs args)
{
//Spawn position is stored on the server - Our command is called on the server so we can turn our IPlayer into IServerPlayer.
IServerPlayer player = args.Caller.Player as IServerPlayer;
player.Entity.TeleportTo(player.GetSpawnPosition(false));
return TextCommandResult.Success();
}
}
}
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 and booting into a world, 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.
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:
- It's a .cs (source) mod and not a .dll (compiled) mod. Refer here for how to set a 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, "TipMod", is not present in the mod folder.
Distribution
To distribute this mod, run the following command in the vsmodtools cli pack <your mod id>
, then copy the .zip file into your VintageStory mods folder.
Here are the official versions:
- for VS v1.13.2: TipMod_vs1.13.2_v1.0.0.zip and SpawnMod_vs1.13.2_v1.0.0.zip.
Wondering where some links have gone?
The modding navbox is going through some changes! Check out Navigation Box Updates for more info and help finding specific pages.
Modding | |
---|---|
Modding Introduction | Getting Started • Theme Pack |
Content Modding | Content Mods • Developing a Content Mod • Basic Tutorials • Intermediate Tutorials • Advanced Tutorials • Content Mod Concepts |
Code Modding | Code Mods • Setting up your Development Environment |
Property Overview | Item • Entity • Entity Behaviors • Block • Block Behaviors • Block Classes • Block Entities • Block Entity Behaviors • Collectible Behaviors • World properties |
Workflows & Infrastructure | Modding Efficiency Tips • Mod-engine compatibility • Mod Extensibility • VS Engine |
Additional Resources | Community Resources • Modding API Updates • Programming Languages • List of server commands • List of client commands • Client startup parameters • Server startup parameters Example Mods • API Docs • GitHub Repository |