Modding:Moddable Mod: Difference between revisions

From Vintage Story Wiki
m
Updated to 1.19.4. Using new command api.
m (Updated to 1.19.4. Using new command api.)
 
(5 intermediate revisions by 4 users not shown)
Line 1: Line 1:
{{GameVersion|1.13.2}}
<languages/><translate>
<!--T:1-->
{{GameVersion|1.19.4}}
__FORCETOC__
__FORCETOC__
This article requires 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
Line 6: Line 8:
If you are looking to create your first code mod, we recommend starting [https://wiki.vintagestory.at/index.php?title=Modding:Advanced_Blocks here] instead.
If you are looking to create your first code mod, we recommend starting [https://wiki.vintagestory.at/index.php?title=Modding:Advanced_Blocks here] instead.


== Introduction ==
== Introduction == <!--T:2-->


<!--T:3-->
VintageStory makes all loaded mods available through it's expansive API, which you can explore in [http://apidocs.vintagestory.at/api/Vintagestory.API.Common.ICoreAPI.html the API docs.]
VintageStory makes all loaded mods available through it's expansive API, which you can explore in [http://apidocs.vintagestory.at/api/Vintagestory.API.Common.ICoreAPI.html the API docs.]
This makes it possible for code mods to interact with eachother and access eachother's public properties.
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. 


<!--T:4-->
The loading and retrieving of mods is done through the [http://apidocs.vintagestory.at/api/Vintagestory.API.Common.IModLoader.html <code>IModLoader</code>], which we retrieve from [http://apidocs.vintagestory.at/api/Vintagestory.API.Common.ICoreAPI.html <code>ICoreAPI</code>]. We can retrieve both the full [http://apidocs.vintagestory.at/api/Vintagestory.API.Common.Mod.html <code>Mod</code>], with all it's useful data, as well as it's contained [http://apidocs.vintagestory.at/api/Vintagestory.API.Common.ModSystem.html <code>ModSystems</code>].
The loading and retrieving of mods is done through the [http://apidocs.vintagestory.at/api/Vintagestory.API.Common.IModLoader.html <code>IModLoader</code>], which we retrieve from [http://apidocs.vintagestory.at/api/Vintagestory.API.Common.ICoreAPI.html <code>ICoreAPI</code>]. We can retrieve both the full [http://apidocs.vintagestory.at/api/Vintagestory.API.Common.Mod.html <code>Mod</code>], with all it's useful data, as well as it's contained [http://apidocs.vintagestory.at/api/Vintagestory.API.Common.ModSystem.html <code>ModSystems</code>].


<!--T:5-->
This is superbly useful in multiple scenarios, for example -
This is superbly useful in multiple scenarios, for example -
* Splitting up a large mod into smaller logical parts that communicate - a modpack.
* 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.
* Extendable mods, for example - the creative GUI tool for growing trees, which lets you register new tree types and easily test them.


<!--T:6-->
In this article, we will be creating two mods. Our first mod will provide a method 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 <code>ModLoader</code> and also explain how to reference an external mod and have that reference
We will demonstrate the use of the <code>ModLoader</code> and also explain how to reference an external mod and have that reference
resolve at game runtime.  
resolve at game runtime.  


<!--T:7-->
<small>Note that, excluding the use of reflection or the use of an intermediary library, which is outside the scope of this article,
<small>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 can interact with eachother</b>.</small>
<b>only compiled mods can interact with eachother</b>.</small>


== TipMod & SpawnMod ==
== TipMod & SpawnMod == <!--T:8-->


<!--T:9-->
The two mods we will be creating are named "TipMod" and "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.
* "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.
* "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 ==
== Preparation == <!--T:10-->
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 <code>TipMod.cs</code> and <code>SpawnMod.cs</code> respectively.
In each mod, we add a new *.cs source file - we will be naming these files <code>TipMod.cs</code> and <code>SpawnMod.cs</code> respectively.
Line 40: Line 48:
* Make the <code>TipMod</code> class <code>public</code> so that it's accessible by other mods.
* Make the <code>TipMod</code> class <code>public</code> so that it's accessible by other mods.


<!--T:11-->
<syntaxhighlight lang="c#">
<syntaxhighlight lang="c#">
// In TipMod.cs
// In TipMod.cs
Line 50: Line 59:
using Vintagestory.API.Server;
using Vintagestory.API.Server;


<!--T:12-->
namespace tipmod.src
namespace tipmod.src
{
{
Line 62: Line 72:
</syntaxhighlight>
</syntaxhighlight>


<!--T:13-->
<syntaxhighlight lang="c#">
<syntaxhighlight lang="c#">
// In SpawnMod.cs
// In SpawnMod.cs
Line 72: Line 83:
using Vintagestory.API.Server;
using Vintagestory.API.Server;


<!--T:14-->
namespace spawnmod.src
namespace spawnmod.src
{
{
Line 86: Line 98:
where we will define the data structure for tips, called <code>Tip</code>.
where we will define the data structure for tips, called <code>Tip</code>.


<!--T:15-->
<syntaxhighlight lang="c#">
<syntaxhighlight lang="c#">
// In Tip.cs
// In Tip.cs
Line 96: Line 109:
using System.Threading.Tasks;
using System.Threading.Tasks;


<!--T:16-->
namespace tipmod.src
namespace tipmod.src
{
{
Line 103: Line 117:
         string tip;
         string tip;


         public Tip(string author, string tip)
         <!--T:17-->
public Tip(string author, string tip)
         {
         {
             this.author = author;
             this.author = author;
Line 109: Line 124:
         }
         }


         public override string ToString() {
         <!--T:18-->
public override string ToString() {
             return "Tip by \"" + author + "\": " + tip;  
             return "Tip by \"" + author + "\": " + tip;  
         }
         }
Line 119: Line 135:
We are now ready to extend our mods, starting with "TipMod" - our moddable mod.
We are now ready to extend our mods, starting with "TipMod" - our moddable mod.


== Setting up TipMod ==
== Setting up TipMod == <!--T:19-->
For "TipMod", we do the following:
For "TipMod", we do the following:
* Store the <code>ICoreServerAPI</code> object as a field so that we can access it throughout our class.
* Store the <code>ICoreServerAPI</code> object as a field so that we can access it throughout our class.
Line 127: Line 143:
* Define a <code>public</code> method for adding new <code>Tip</code> objects.
* Define a <code>public</code> method for adding new <code>Tip</code> objects.


<!--T:20-->
<syntaxhighlight lang="c#">
<syntaxhighlight lang="c#">
     // In TipMod.cs
     // In TipMod.cs
Line 133: Line 150:
         ICoreServerAPI api;
         ICoreServerAPI api;


         List<Tip> tips = new List<Tip>();
         <!--T:21-->
List<Tip> tips = new List<Tip>();
         double tipInterval = 10;
         double tipInterval = 10;


         public override void StartServerSide(ICoreServerAPI api)
         <!--T:22-->
public override void StartServerSide(ICoreServerAPI api)
         {
         {
             base.StartServerSide(api);
             base.StartServerSide(api);
             this.api = api;
             this.api = api;


             api.Event.Timer(OnInterval, tipInterval);
             <!--T:23-->
api.Event.Timer(OnInterval, tipInterval);
         }
         }


         private void OnInterval()
         <!--T:24-->
private void OnInterval()
         {
         {
             int tipCount = tips.Count();
             int tipCount = tips.Count();
             string message = "There aren't any listed tips";
             string message = "There aren't any listed tips";


             if (tipCount > 0)
             <!--T:25-->
if (tipCount > 0)
             {
             {
                 // Select a random number in the range of [0-1]
                 // Select a random number in the range of [0-1]
Line 156: Line 178:
                 int randomTip = (int)Math.Floor(random * tipCount);
                 int randomTip = (int)Math.Floor(random * tipCount);


                 Tip tip = tips[randomTip];
                 <!--T:26-->
Tip tip = tips[randomTip];


                 message = tip.ToString();
                 <!--T:27-->
message = tip.ToString();
             }  
             }  




             api.SendMessageToGroup(GlobalConstants.GeneralChatGroup, message, EnumChatType.AllGroups);
             <!--T:28-->
api.SendMessageToGroup(GlobalConstants.GeneralChatGroup, message, EnumChatType.AllGroups);
         }
         }


         public void AddTip(Tip tip)
         <!--T:29-->
public void AddTip(Tip tip)
         {
         {
             tips.Add(tip);
             tips.Add(tip);
Line 173: Line 199:
For testing purposes, we set the interval to be very short (10 seconds). Feel free to change this accordingly.
For testing purposes, we set the interval to be very short (10 seconds). Feel free to change this accordingly.


<!--T:30-->
Remember, both <b>classes and methods which other mods will interact with have to be declared <code>public</code>.</b>
Remember, both <b>classes and methods which other mods will interact with have to be declared <code>public</code>.</b>
In C#, classes and methods are not public by default. You can [https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/access-modifiers read more about this here.]
In C#, classes and methods are not public by default. You can [https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/access-modifiers 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".
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 ==
== Setting up SpawnMod == <!--T:31-->
Let's first setup our mods functionality. We register a command which teleports the player to spawn. Conveniently, the [http://apidocs.vintagestory.at/api/Vintagestory.API.Server.IServerPlayer.html <code>IServerPlayer</code>] object stores it's spawn position, as well as the [http://apidocs.vintagestory.at/api/Vintagestory.API.Common.IPlayer.html#Vintagestory_API_Common_IPlayer_Entity <code>IPlayerEntity</code>], which defines a method for teleporting itself to a location.  
Let's first setup our mods functionality. We register a command which teleports the player to spawn. Conveniently, the [http://apidocs.vintagestory.at/api/Vintagestory.API.Server.IServerPlayer.html <code>IServerPlayer</code>] object stores it's spawn position, as well as the [http://apidocs.vintagestory.at/api/Vintagestory.API.Common.IPlayer.html#Vintagestory_API_Common_IPlayer_Entity <code>IPlayerEntity</code>], which defines a method for teleporting itself to a location.  
<syntaxhighlight lang="c#">
<syntaxhighlight lang="c#">
Line 187: Line 214:
             base.StartServerSide(api);
             base.StartServerSide(api);


             api.RegisterCommand("spawn", "Teleport to spawn", "", OnCmdSpawn);
             api.ChatCommands.Create("spawn")
                .WithDescription("Teleport to spawn")
                .RequiresPlayer()
                .RequiresPrivilege(Privilege.chat)
                .HandleWith(OnCmdSpawn);
         }
         }


         private void OnCmdSpawn(IServerPlayer player, int groupId, CmdArgs args)
        <!--T:33-->
         private TextCommandResult OnCmdSpawn(TextCommandCallingArgs args)
         {
         {
             player.Entity.TeleportTo(player.SpawnPosition);
            //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();
         }
         }
     }
     }
Line 199: Line 234:
If our mods are working, we're ready to make "SpawnMod" interact with "TipMod".
If our mods are working, we're ready to make "SpawnMod" interact with "TipMod".


== Mods interacting ==
== Mods interacting == <!--T:34-->
Before we get started, we have to add a reference in "SpawnMod" to "TipMod".  
Before we get started, we have to add a reference in "SpawnMod" to "TipMod".  
=== Adding a reference to an external mod ===
=== Adding a reference to an external mod ===
Line 205: Line 240:
[[File:ADDINGAREFERENCERIGHTCLICK.png|none]]
[[File:ADDINGAREFERENCERIGHTCLICK.png|none]]


<!--T:35-->
Here we locate either the "TipMod" project or the compiled .dll file, and add it.
Here we locate either the "TipMod" project or the compiled .dll file, and add it.
[[File:FindingReference.png|none]]
[[File:FindingReference.png|none]]


<!--T:36-->
After adding the reference, make sure it is not copying the file to the output folder when compiling -
After adding the reference, make sure it is not copying the file to the output folder when compiling -
having multiple .dll files with <code>ModSystems</code> in them will break your mod.
having multiple .dll files with <code>ModSystems</code> in them will break your mod.
[[File:SettingCopyLocalToFalse.png|none]]
[[File:SettingCopyLocalToFalse.png|none]]


=== Accessing the ModLoader ===
=== Accessing the ModLoader === <!--T:37-->
Now we're ready to have "SpawnMod" add tips. Let's go ahead and do the following.
Now we're ready to have "SpawnMod" add tips. Let's go ahead and do the following.
* Add a <code>using</code> directive for the namespace <code>tipmod.src</code>.
* Add a <code>using</code> directive for the namespace <code>tipmod.src</code>.
Line 218: Line 255:
* Call the <code>AddTip</code> method provided by <code>TipMod</code> and add a variety of tips.
* Call the <code>AddTip</code> method provided by <code>TipMod</code> and add a variety of tips.


<!--T:38-->
<syntaxhighlight lang="c#">
<syntaxhighlight lang="c#">
         public override void StartServerSide(ICoreServerAPI api)
         public override void StartServerSide(ICoreServerAPI api)
Line 223: Line 261:
             base.StartServerSide(api);
             base.StartServerSide(api);


             api.RegisterCommand("spawn", "Teleport to spawn", "", OnCmdSpawn);
             api.ChatCommands.Create("spawn")
 
                .WithDescription("Teleport to spawn")
                .RequiresPlayer()
                .RequiresPrivilege(Privilege.chat)
                .HandleWith(OnCmdSpawn);
           
             using tipmod.src;
             using tipmod.src;
             TipMod tipMod = api.ModLoader.GetModSystem<TipMod>();
             TipMod tipMod = api.ModLoader.GetModSystem<TipMod>();
Line 233: Line 275:
</syntaxhighlight>
</syntaxhighlight>


== Conclusion ==
== Conclusion == <!--T:41-->


<!--T:42-->
Following the steps in this article, this is what our resulting code looks like, spread across two mods.
Following the steps in this article, this is what our resulting code looks like, spread across two mods.


<!--T:43-->
<syntaxhighlight lang="c#">
<syntaxhighlight lang="c#">
// In TipMod.cs
// In TipMod.cs
Line 248: Line 292:
using Vintagestory.API.Server;
using Vintagestory.API.Server;


<!--T:44-->
namespace tipmod.src
namespace tipmod.src
{
{
Line 254: Line 299:
         ICoreServerAPI api;
         ICoreServerAPI api;


         List<Tip> tips = new List<Tip>();
         <!--T:45-->
List<Tip> tips = new List<Tip>();
         double tipInterval = 10;
         double tipInterval = 10;


         public override void StartServerSide(ICoreServerAPI api)
         <!--T:46-->
public override void StartServerSide(ICoreServerAPI api)
         {
         {
             base.StartServerSide(api);
             base.StartServerSide(api);
             this.api = api;
             this.api = api;


             api.Event.Timer(OnInterval, tipInterval);
             <!--T:47-->
api.Event.Timer(OnInterval, tipInterval);
         }
         }


         private void OnInterval()
         <!--T:48-->
private void OnInterval()
         {
         {
             int tipCount = tips.Count();
             int tipCount = tips.Count();
             string message = "There aren't any listed tips";
             string message = "There aren't any listed tips";


             if (tipCount > 0)
             <!--T:49-->
if (tipCount > 0)
             {
             {
                 // Select a random number in the range of [0-1]
                 // Select a random number in the range of [0-1]
Line 277: Line 327:
                 int randomTip = (int)Math.Floor(random * tipCount);
                 int randomTip = (int)Math.Floor(random * tipCount);


                 Tip tip = tips[randomTip];
                 <!--T:50-->
Tip tip = tips[randomTip];


                 message = tip.ToString();
                 <!--T:51-->
message = tip.ToString();
             }  
             }  




             api.SendMessageToGroup(GlobalConstants.GeneralChatGroup, message, EnumChatType.AllGroups);
             <!--T:52-->
api.SendMessageToGroup(GlobalConstants.GeneralChatGroup, message, EnumChatType.AllGroups);
         }
         }


         public void AddTip(Tip tip)
         <!--T:53-->
public void AddTip(Tip tip)
         {
         {
             tips.Add(tip);
             tips.Add(tip);
Line 303: Line 357:
using System.Threading.Tasks;
using System.Threading.Tasks;


<!--T:54-->
namespace tipmod.src
namespace tipmod.src
{
{
Line 310: Line 365:
         string tip;
         string tip;


         public Tip(string author, string tip)
         <!--T:55-->
public Tip(string author, string tip)
         {
         {
             this.author = author;
             this.author = author;
Line 316: Line 372:
         }
         }


         public override string ToString() {
         <!--T:56-->
public override string ToString() {
             return "Tip by \"" + author + "\": " + tip;  
             return "Tip by \"" + author + "\": " + tip;  
         }
         }
Line 333: Line 390:
using tipmod.src;
using tipmod.src;


<!--T:57-->
namespace spawnmod.src
namespace spawnmod.src
{
{
Line 341: Line 399:
             base.StartServerSide(api);
             base.StartServerSide(api);


             api.RegisterCommand("spawn", "Teleport to spawn", "", OnCmdSpawn);
             api.ChatCommands.Create("spawn")
                .WithDescription("Teleport to spawn")
                .RequiresPlayer()
                .RequiresPrivilege(Privilege.chat)
                .HandleWith(OnCmdSpawn);


             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"));
             tipMod.AddTip(new Tip("codemeister32", "Can't find your way home? 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"));
             tipMod.AddTip(new Tip("codemeister32", "Being chased by wolves? Quick, type /spawn"));
         }
         }


         private void OnCmdSpawn(IServerPlayer player, int groupId, CmdArgs args)
         private TextCommandResult OnCmdSpawn(TextCommandCallingArgs args)
         {
         {
             player.Entity.TeleportTo(player.SpawnPosition);
            //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();
         }
         }
     }
     }
Line 358: Line 423:
</syntaxhighlight>
</syntaxhighlight>


<!--T:62-->
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.
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.
[[File:ModdableModSuccess.png|none]]
[[File:ModdableModSuccess.png|none]]
If so, well done! You've successfully followed the article and have created a moddable mod.
If so, well done! You've successfully followed the article and have created a moddable mod.


== Troubleshooting ==
== Troubleshooting == <!--T:63-->


<!--T:64-->
If you've ran into problems setting up either mod, check the error logs located at <code>VintagestoryData/Logs</code> in <code>server-main.txt</code>.
If you've ran into problems setting up either mod, check the error logs located at <code>VintagestoryData/Logs</code> in <code>server-main.txt</code>.
Possible causes for either mod not working:
Possible causes for either mod not working:
Line 372: Line 439:
* The referenced mod, "TipMod", is not present in the mod folder.
* The referenced mod, "TipMod", is not present in the mod folder.


== Distribution ==
== Distribution == <!--T:65-->
To distribute this mod, run the following command in the vsmodtools cli <code>pack <your mod id></code>, then copy the .zip file into your VintageStory mods folder.
To distribute this mod, run the following command in the vsmodtools cli <code>pack <your mod id></code>, then copy the .zip file into your VintageStory mods folder.


<!--T:66-->
Here are the official versions:
Here are the official versions:
* for VS v1.13.2: [https://wiki.vintagestory.at/index.php?title=File:TipMod_vs1.13.2_v1.0.0.zip TipMod_vs1.13.2_v1.0.0.zip] and [https://wiki.vintagestory.at/index.php?title=File:SpawnMod_vs1.13.2_v1.0.0.zip SpawnMod_vs1.13.2_v1.0.0.zip].
* for VS v1.13.2: [https://wiki.vintagestory.at/index.php?title=File:TipMod_vs1.13.2_v1.0.0.zip TipMod_vs1.13.2_v1.0.0.zip] and [https://wiki.vintagestory.at/index.php?title=File:SpawnMod_vs1.13.2_v1.0.0.zip SpawnMod_vs1.13.2_v1.0.0.zip].


<!--T:67-->
{{Navbox/modding|Vintage Story}}
{{Navbox/modding|Vintage Story}}
</translate>
Confirmedusers
556

edits