Modding:Code Tutorial Simple Block

From Vintage Story Wiki
Other languages:
  • English

Introduction

Objective

In this tutorial, you will be introduced to code modding by creating a block with custom functionality. You will create the assets for this project, find out how block classes are registered, and use a number of different functions. The block you will be creating is a trampoline, where as an entity collides with it, they will bounce.

Please note that this tutorial is quite detailed since it is the first in the code tutorial series. If you are new to modding, it is highly recommended to follow each of these steps.

Prerequisites

It is recommended to use the same project for all newer code tutorials, with the project name "VSTutorial". If you have not yet done this, please follow the following tutorials:

  1. Preparing For Code Mods
  2. Creating A Code Mod

This tutorial also assumes that you have read:

Assets

VisualStudioOpenAssetsFolderInExplorerNeat.png

Before you get started with any coding, it is important that you create the assets for your new trampoline block.

In Visual Studio, your mod's assets are easy to access from the solution explorer. Right click on the folder called assets, and then select Open Folder in File Explorer. This will open the assets folder for your mod, which is an identical format to the assets folder in content mods. The opened folder should only contain another folder labeled vstutorial.

You need to add a few asset files for the trampoline. As always, these can be downloaded from GitHub here.

The downloaded zip file contains a folder called vstutorial. Copy this folder over your own, and the asset files will be added. You can verify this by going back to Visual Studio, looking at the solution explorer, and expanding the assets folder. You should now have a trampoline block type, as well as a lang and texture file.

Run the game, launch a world, and check the creative menu. You should see the trampoline block. It looks fancy, but has absolutely no functionality yet.

Playing Vintage Story in fullscreen mode?
It is highly recommended to run the game in windowed mode when making code mods. If your code results in an error, Visual Studio will attempt to gain focus, and Vintage Story will stop responding. You can press F11 to easily switch between the fullscreen and windowed modes.

Block Class

Close the game and go back to Visual Studio. To add custom functionality to the trampoline block, you'll need to create a block class.

A block class is a script that offers custom functionality for any specific blocktype assets. Note that each blocktype can only have a single attached block class.

Creating a Block Class

Before you create a new block class, you should create a new folder called 'Blocks'. It is good practice to keep your classes and other scripts in folders relevant to what they are. In the solution explorer, right click on VSTutorial (the project, not the one labelled solution), hover over Add, and click New Folder. You can then enter a name for your folder.

You need a name for your new block's class. Generally, classes in Vintage Story follow the naming convention of {Type}{Name}. So, in the example of a trampoline block class, you should call this block class "BlockTrampoline".

With your folder now created, you need to create a new class. Right click the new folder, hover over Add, and select Class. This will open a menu with a lot of templates, and an input field to enter your class name. Make sure you have selected the standard Class at the top of the template list, and enter the name 'BlockTrampoline.cs'. Click Add, and your class will be created.

The trampoline block's class should open automatically, but you can open it manually by double clicking its entry in the solution explorer.

Explaining Block Classes

Let's have a quick run down of how a block class works. Replace the entire contents of the new class with the following code:

BlockTrampoline.cs
//Here are the imports for this script. Most of these will add automatically.
using Vintagestory.API.Common;
using Vintagestory.API.MathTools;

/*
* The namespace the class will be in. This is essentially the folder the script is found in.
* If you need to use the BlockTrampoline class in any other script, you will have to add 'using VSTutorial.Blocks' to that script.
*/
namespace VSTutorial.Blocks
{
    /*
    * The class definition. Here, you define BlockTrampoline as a child of Block, which
    * means you can 'override' many of the functions within the general Block class. 
    */
    internal class BlockTrampoline : Block
    {
        //Any code within this 'override' function will be called when a trampoline block is placed. 
        public override void OnBlockPlaced(IWorldAccessor world, BlockPos blockPos, ItemStack byItemStack = null)
        {
            //Log a message to the console.
            api.Logger.Event("Trampoline Block Placed!");
            //Perform any default logic when our block is placed.
            base.OnBlockPlaced(world, blockPos, byItemStack);
        }

        //Any code within this 'override' function will be called when a trampoline block is broken.
        public override void OnBlockBroken(IWorldAccessor world, BlockPos pos, IPlayer byPlayer, float dropQuantityMultiplier = 1)
        {
            //Log a message to the console.
            api.Logger.Event("Trampoline Block Broken!");
            //Perform any default logic when our block is broken (e.g., dropping the block as an item.)
            base.OnBlockBroken(world, pos, byPlayer, dropQuantityMultiplier);
        }
    }
}

Note the code comments throughout the file. These are marked by double slashes ( // ) or any text between /* and */. These give brief explanations on what every line of this example does.

For further explanation, on line 15, the BlockTrampoline class is defined as a child of the Block class, which is in turn a child of the CollectibleObject class, and then the RegistryObject class. This essentially means that any function within these three inherited classes marked as 'virtual', 'abstract', or 'override' can be given custom functionality. For an exhaustive list of functions and attributes that can be overriden, take a look at the API documentation for the Block and CollectibleObject methods.

Why so many code comments?
There is much debate about how many code comments are too many, and when/where they should be used. Although many people who are experienced with C# or other C programming languages may find this code easy to understand, this tutorial is designed for those who may not have ever used a C programming language, or even any programming language.

Registering the Block Class

The class for the trampoline class is created, however before it can be used it needs to be registered through the game API. When you created your mod, a mod system will have been automatically made. Look for it in your solution explorer, it should be called VSTutorialModSystem, and double click it to open it.

Your mod system will likely contain three functions at this point - Start, StartServerSide, and StartClientSide. The server and client side functions are not required here, so feel free to delete them or keep them in for the future. Either is fine.

In the Start function, you need to make a call to the RegisterBlockClass function in the API class. Add the following code on a new line inside of the Start function's block:

api.RegisterBlockClass();

You'll notice that, currently, this results in an error. Hover over the 'RegisterBlockClass' with your mouse and it will show you a breakdown of the function.

VSBlockTutorialRegisterBlockClassFunction.png

This tells you the function's parameters and a description of what it does. Not all functions in the code have descriptions attached to them, however as the modding API becomes more documented, these will hopefully become more populated. In this particular case, you need to pass in a class name, and a block type to the function.

The class name argument should be fully lowercase, and should generally be your mod ID, joined with the name of the class you just made. The blockType will be the class you just made. Replace the function with the following:

api.RegisterBlockClass(Mod.Info.ModID + ".trampoline", typeof(BlockTrampoline));

This will register the BlockTrampoline class with the name "vstutorial.trampoline".

Why include the mod id here?
If you've made code mods before the introduction of these new tutorials, you probably haven't included your mod ID when registering classes. As more and more mods are being made, there are occasional collisions in regards to class names. Say, for example, two mods both add in a 'trampoline' block class. Only one instance of the trampoline class will exist, which can cause issues if they had slightly different functionality.

So, including your mod ID when registering classes will ensure that these 'collisions' do not occur.

Adding Block Class to Asset

Before your block class will work, you need to add a new property to the blocktype JSON asset. In the solution explorer, open the file at assets/vstutorial/blocktypes/trampoline.json.

You need to add the following property to the file:

"class": "vstutorial.trampoline",

Generally, this element is placed as the second property in your json file, immediately below the code property.

Note that the value of this property is identical to the value we used in the RegisterBlockClass function. This is how the game links the JSON asset files to your code's registered classes.

Testing the Block Class

Press F5 again to run the game with the mod. Remember to set the game to windowed mode by pressing F11. Find the trampoline in the creative menu, place one, and destroy it.

After doing so, take a look at the console that opened when launching the game. You should be able to see the following logs:

[Client Event] Trampoline Block Placed!
[Server Event] Trampoline Block Placed!
[Client Event] Trampoline Block Broken!
[Server Event] Trampoline Block Broken!

Wait... what?

The trampoline block was placed once, and broken once, so why are there two entries? Notice that each entry tells you whether it came from the client or server, and in this case, we have one of each. Due to running a singleplayer instance, there is both a client and server running at the same time. The overriden functions are being called once on the client, and once on the server.

In many instances, this is exactly what is wanted. Most features are synced between the client and server, but it is important to remember that many functions get called twice in this system. There are a number of ways you can verify what 'side' is calling the function, or to limit certain code to one side.

Anyway, it's clear that the trampoline class is working, so close the game and go back to the BlockTrampoline class.

Trampoline Functionality

You need to create the actual functionality for the trampoline. When an entity collides with this block, the entity's vertical velocity should be reversed and multiplied by a certain amount.

The placed and break functions are unnecessary for this block, so you can remove them. Your script should look like the following:

BlockTrampoline.cs
//Here are the imports for this script. Most of these will add automatically.
using Vintagestory.API.Common;
using Vintagestory.API.MathTools;

/*
* The namespace the class will be in. This is essentially the folder the script is found in.
* If you need to use the BlockTrampoline class in any other script, you will have to add 'using VSTutorial.Blocks' to that script.
*/
namespace VSTutorial.Blocks
{
    /*
    * The class definition. Here, you define BlockTrampoline as a child of Block, which
    * means you can 'override' many of the functions within the general Block class. 
    */
    internal class BlockTrampoline : Block
    {
        
    }
}
VisualStudioListBlockMethodsFromOverride.png

Click on the empty space, and type 'override ' (including the space). Visual Studio will show you a scrollable list of functions that can be overriden. If the menu closes, you can simply delete the space and replace it, and the menu will return.

Feel free to scroll through this menu and take a look at what is available. The functions here are all listed in the API documentation mentioned earlier.

The function you need to use is called OnEntityCollide. With the mentioned menu above, type 'collide', and you should see a single result in the list. Press enter, tab, or double click on the entry, and Visual Studio will generate the function for you.

Take a look at the supplied arguments for this function. You have access to the world, the entity, the block's position, the 'facing' position of the collision, the collision speed, and whether this is an impact.

Making it Bounce

The following points determine when an entity should bounce on the trampoline block.

  1. The entity should bounce in the moment it lands on top of the block, and not if it is standing on it already. Therefore, isImpact needs to be true.
  2. The entity should be colliding vertically. The sides of the block shouldn't push an entity away. In effect, facing.IsVertical needs to be true.

So, add the following block inside the function:

if (isImpact && facing.IsVertical)
{
    
}

Now, you need to flip the motion of the entity. To do this, you can change the value at entity.Pos.Motion.Y. Add the following code inside the if block.

entity.Pos.Motion.Y *= -0.8f;

This is a quicker way of writing:

entity.Pos.Motion.Y = entity.Pos.Motion.Y * -0.8f;

Multiplying by -0.8 will result in reversing the velocity, and reducing it by 20%. Feel free to play with this value to see some different effects.

The final script should look like the following:

BlockTrampoline.cs
//Here are the imports for this script. Most of these will add automatically.
using Vintagestory.API.Common;
using Vintagestory.API.Common.Entities;
using Vintagestory.API.MathTools;

/*
* The namespace the class will be in. This is essentially the folder the script is found in.
* If you need to use the BlockTrampoline class in any other script, you will have to add 'using VSTutorial.Blocks' to that script.
*/
namespace VSTutorial.Blocks
{
    /*
    * The class definition. Here, you define BlockTrampoline as a child of Block, which
    * means you can 'override' many of the functions within the general Block class. 
    */
    internal class BlockTrampoline : Block
    {
        public override void OnEntityCollide(IWorldAccessor world, Entity entity, BlockPos pos, BlockFacing facing, Vec3d collideSpeed, bool isImpact)
        {
            if (isImpact && facing.IsVertical)
            {
                entity.Pos.Motion.Y *= -0.8f;
            }
        }
    }
}

Give it a test! Falling onto your trampoline block should cause you to bounce!

Finished Project

You can download the entire finished project from GitHub here. This is a full project setup that includes code comments on how everything works.

Conclusion

Congratulations, you've created, registered and tested a new block class! Although it's been a long tutorial, you've covered many topics here that will give you a great understanding of how coding for Vintage Story works.

Next Steps...

If you want to test your knowledge consider doing the tasks under the Going Further section below.

When you're ready, take a look at the next tutorial. This will show you how to give extra functionality to an item.

Going Further

Want to make some additional changes to this mod? Try and achieve the following things!

Make the trampoline super bouncy.

To achieve this...
Replace the -0.8 value with -1.5 or lower. Although setting to -1 would technically reflect the motion perfectly, some velocity is lost on the frame the entity collides with the block.

Change the functionality of the block to allow an entity to bounce on any side of the block. You should use facing.IsAxisNS and facing.IsAxisWE to determine what side the entity collides with.

To achieve this...
You need to replace the contents of the OnEntityCollide function with something similar to the following:
if (isImpact && facing.IsVertical)
{
    entity.Pos.Motion.Y *= -0.8f;
}
else if (facing.IsAxisNS)
{
    entity.Pos.Motion.Z *= -1.2f;
}
else if (facing.IsAxisWE)
{
    entity.Pos.Motion.X *= -1.2f;
}
Note that you will need to jump into the trampoline block to bounce off of it.
Code Modding
Basics Code Mods Preparing For Code Mods Creating A Code Mod
Tutorials
Advanced Server-Client Considerations Setting up your Development Environment Advanced Blocks Advanced Items Block and Item Interactions Block Behavior Block Entity Particle Effects World Access Inventory Handling Commands GUIs Network API Monkey patching (Harmony)
Data Management VCDBS format Savegame Moddata ModConfig File Chunk Moddata Serialization Formats TreeAttribute
Worldgen WorldGen API NatFloat EvolvingNatFloat
Rendering Shaders and Renderers
Icon Sign.png

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 ItemEntityEntity BehaviorsBlockBlock BehaviorsBlock ClassesBlock EntitiesBlock Entity BehaviorsCollectible BehaviorsWorld properties
Workflows & Infrastructure Modding Efficiency TipsMod-engine compatibilityMod ExtensibilityVS Engine
Additional Resources Community Resources Modding API Updates Programming Languages List of server commandsList of client commandsClient startup parametersServer startup parameters
Example ModsAPI DocsGitHub Repository