Modding:Moddable Mod: Difference between revisions
mNo edit summary |
mNo edit summary |
||
Line 2: | Line 2: | ||
- THIS IS CURRENTLY A DRAFT - | - THIS IS CURRENTLY A DRAFT - | ||
This article requires | 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 | 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, | If you are looking to create your first mod, we recommend starting here =Link to first code mod sample=. | ||
== Introduction == | == Introduction == | ||
VintageStory makes | VintageStory makes all loaded mods available through the API. | ||
This makes it possible for code mods to interact with eachother and | 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 | * 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 | 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 | 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 | <b>only compiled mods will be able to resolve eachother's references</b>. | ||
== 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 | 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 | In our .cs files, we do the following. | ||
the base for any VintageStory code mod. | * 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 | ||
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. | ||
* | * 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"; | |||
// | 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, | |||
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 | 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 | 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. | |||
<syntaxhighlight lang="c#"> | <syntaxhighlight lang="c#"> | ||
class SpawnMod : ModSystem | class SpawnMod : ModSystem | ||
Line 195: | Line 204: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
SpawnMod is ready | 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 | 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". | ||
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 - | ||
Now we're ready to have SpawnMod add tips. Let's go ahead and do the following. | |||
* Add a using | * 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> | ||
== 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"; | |||
- | 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>(); | |||
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); | |||
} | |||
} | |||
} | |||
</syntaxhighlight> | |||
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 == | == 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-