Modding:Content Tutorial Entity Creation Part 2
This page was last verified for Vintage Story version 1.21.0.
Entity Creation Part 2 - AI & Spawning
Introduction
Welcome to the second part of the entity creation tutorial. This is the second of a two-part tutorial on creating, registering, and implementing a creature/entity into the game. If you have not completed that tutorial, please first complete it here.
Concept/Implementation Split
This tutorial also follows the concept/implementation split, so each section has been split into two further sections to help understanding. The 'concept' section will give a list of what needs to be achieved and why, and the 'Implementation' section will give the steps to actually achieve that.
Objective
In this tutorial, you will be continuing with the jackrabbit entity file. Specifically, you will implement a basic AI for the creature, and then allow it to spawn naturally in the world. This should give you a good understanding of how entity AIs work in Vintage Story to help you expand and use more complex AI tasks.
Assets
This tutorial uses the assets of the previous one. If you need to, you can download the workspace and assets for this tutorial. The completed files can also be found here.
For this tutorial, you will want to start with the 'entity-tutorial-part1.zip' file.
Prerequisites
The same prerequisites apply to the previous tutorial. Although it is not recommended to complete this tutorial as a standalone tutorial, it is important that you have a good understanding and grasp of JSON files.
Simple Behaviors
Concept
- Most entity behaviors are client and server side. Some entity behaviors are server-side only. There are very few client-side only behaviors.
- Most entities will have behaviors that control their physics.
- Each behavior requires a code, and most behaviors have additional parameters that can be added.
- Behaviors:
- “RepulseAgents” will push nearby entities and allow this entity to be pushed.
- “ControlledPhysics” is a complex behavior that handles collisions and physics. We can control a step height here for the entity, which controls how high the entity can walk up without jumping.
- “Float Up When Stuck” will cause the entity to float upwards if it’s stuck. You can specify for this to only happen if the entity is dead.
- “Interpolate Position” is a client-side behavior that will smoothly move the entity based on the synced server positions.
- “Harvestable” allows the entity to be harvested when dead. The drops for the entity are controlled from the server-side behavior.
- “Despawn” will cause the entity to despawn under certain specified conditions.
- “Health” is a server-side behavior that specifies the maximum health of the entity.
- “DeadDecay” is a server-side behavior that will convert the dead entity into a carcass (or other specified block) after a certain time period.
- “Breathe” is a server-side behavior that will cause an entity to drown if underwater for too long.
- “Task AI” is a complex server-side behavior that controls the entity’s AI. It is built out of AI tasks, which are similar in concept to behaviors and each has its own code and parameters. AI tasks are also used to control what animation is currently playing. You will also need to specify what type of creature this is for the AI to work.
- Most entity behaviors can be found on GitHub at Survival API Behaviors and Essentials API Behaviors.
Implementation
In the previous tutorial, you had to create empty arrays in the client and server properties for behaviors. Any properties in this section are going to be placed within one of these arrays.
Let’s start with the client-side behaviors. These are usually simpler than the server-side ones. Each behavior is its own object in the array, meaning it should be contained within it’s own { } section. For instance, an entity with only a “controlledphysics” behavior will look like this:
"behaviors": [
{
"code": "controlledphysics",
"stepHeight": 1.1251
}
]
Note that the properties for each behavior are listed alongside its code.
With all of the behaviors mentioned in the concept section, our client-side behaviors look like this:
| Client Behaviors JSON |
|---|
"behaviors": [
{
"code": "repulseagents"
},
{
"code": "controlledphysics",
"stepHeight": 1.1251
},
{
"code": "floatupwhenstuck",
"onlyWhenDead": true
},
{
"code": "interpolateposition"
},
{
"code": "harvestable"
},
{
"code": "despawn",
"minPlayerDistance": 8,
"belowLightLevel": 8,
"minSeconds": 300
}
],
|
Most of these behaviors won’t have any effect if they don’t also exist on the server. Add the following to your server behaviors:
| Server Behaviors JSON |
|---|
"behaviors": [
{
"code": "repulseagents"
},
{
"code": "controlledphysics",
"stepHeight": 1.1251
},
{
"code": "despawn",
"minPlayerDistance": 8,
"belowLightLevel": 8,
"minSeconds": 300
},
{
"code": "health",
"currenthealth": 5,
"maxhealth": 5
},
{
"code": "deaddecay",
"hoursToDecay": 96,
"decayedBlock": "carcass-tiny"
},
{
"code": "floatupwhenstuck",
"onlyWhenDead": true
},
{
"code": "harvestable",
"drops": [
{
"type": "item",
"code": "redmeat-raw",
"quantity": {
"avg": 2,
"var": 0
}
},
{
"type": "item",
"code": "hide-raw-small",
"quantity": {
"avg": 0.5,
"var": 0
}
}
]
},
{
"code": "breathe"
},
{
"code": "taskai",
"aiCreatureType": "LandCreature",
"aitasks": []
}
]
|
A few of the outstanding behaviors here are health, dead decay, harvestable, and breathe, as these do not exist or are different in the client behaviors.
You may also notice that the “Task AI” behavior does not have any AI tasks listed. These will be added to in the next section of the tutorial.
After adding both the client and server behaviors, you should be able to test your creature. The jackrabbit should have working physics, can be knocked back, killed, and harvested. Feel free to change the quantity natfloat in the drops to control how common each drop is.
AI Tasks
Concept
- All AI tasks will be contained within the “aitasks” property array in the ‘taskai’ behavior.
- Each AI task has a code. The code is an ID to a scripted AI task that can be configured with further data.
- Entities can have multiple AI tasks with the same code and different parameters.
- Each AI task has a priority. Entities will usually act on whichever valid task has the highest priority at the time.
- Some tasks will end when they are no longer valid. Other tasks require min and max duration properties. Many of these tasks will also have a min and max cooldown.
- AI Tasks can specify a registered animation in the entity file. The animation will be played while the AI task takes place. An animation speed can also be specified.
- Some AI tasks can target specific entities.
Implementation
Let’s create an instance of an AI task, just to see what we can do.
In the “aitasks” array, add the following object:
{
"code": "lookaround",
"priority": 0.5
}
The properties here are fundamental to an AI task. The code “lookaround” tells us that the AiTaskLookAround class (found here on GitHub) will control what this does to the entity. The priority of 0.5 is quite low, and is the lowest of all the AI tasks that will be added to the jackrabbit. If it has nothing else to do, it’ll begin looking around.
Test the entity now. Given a short time, the entity should start looking around. You’re one step closer to making it look alive.
That’s not very exciting, so add the following task alongside the look around task. Note that AI tasks are usually in order of priority in the file, but this is optional:
{
code: "wander",
priority: 1.0,
priorityForCancel: 1.35,
movespeed: 0.01,
animation: "Walk",
preferredLightLevel: 20
},
Going through this task, there are four new properties that haven’t been shown before. As before, the code property will specify what AI task we’re running. “Wander” will cause the entity to wander around its spawn location.
The priority here is noteworthy. With a priority of 1.0, this task will now be checked before the previous “lookaround” task. If the entity cannot find a valid location to wander to, then it will not be a valid task, and the “lookaround” task will run. This means that the entity will almost always wander around unless it is stuck.
In general, if a task has a higher priority than the one running, then execution of the running task will stop and the higher priority task will start. By using “priorityForCancel”, you can specify that a task should not be interrupted unless there is a valid task with a priority greater than this value. In this instance, only tasks with a priority higher than 1.35 can interrupt the entity from wandering.
Movespeed controls how fast the entity will move when wandering.
As mentioned before, animation will control the animation that plays whilst the entity moves. This has to be an animation that we have registered in the client properties in the previous tutorial.
Preferred light level is a property specific to AiTaskWander, that will cause an entity to look for a block that is lit a specific amount, taking into consideration sunlight and block light.
After adding this AI task, feel free to test the entity again and you’ll see that you have a pretty non-stop hare running around. Without any other AI tasks, they won’t stop until they get stuck.
Let’s add the rest of the AI tasks. The full AI tasks property should look like this:
| AI Tasks Property |
|---|
"aitasks": [
{
"code": "fleeentity",
"entityCodes": [ "game:player", "game:wolf-male", "game:wolf-female", "game:fox-*", "game:hyena-male", "game:hyena-female", "game:bear-*" ],
"priority": 2,
"movespeed": 0.06,
"seekingRange": 14,
"instafleeOnDamageChance": 1,
"animation": "Run",
"animationSpeed": 2
},
{
"code": "getoutofwater",
"priority": 1.4,
"movespeed": 0.015,
"animation": "Walk",
"animationSpeed": 2.2
},
{
"code": "idle",
"priority": 1.38,
"minduration": 200000,
"maxduration": 800000,
"mincooldown": 10000,
"maxcooldown": 30000,
"priorityForCancel": 1.38,
"animation": "Sleep",
"whenNotInEmotionState": "aggressiveondamage",
"duringDayTimeFrames": [
{
"fromHour": 10,
"toHour": 18
},
{
"fromHour": 1,
"toHour": 4
}
],
"stopOnNearbyEntityCodes": [ "game:player", "game:wolf-male", "game:wolf-female", "game:fox-*", "game:hyena-male", "game:hyena-female" ],
"stopRange": 8,
"stopOnHurt": true
},
{
"code": "idle",
"priority": 1.2,
"priorityForCancel": 1.35,
"minduration": 2500,
"maxduration": 2500,
"mincooldown": 6000,
"maxcooldown": 20000,
"animation": "Sniff",
"animationSpeed": 1.25
},
{
"code": "idle",
"priority": 1.1,
"minduration": 1000,
"maxduration": 3000,
"mincooldown": 2000,
"maxcooldown": 30000,
"animation": "Idle"
},
{
"code": "wander",
"priority": 1.0,
"priorityForCancel": 1.35,
"movespeed": 0.01,
"animation": "Walk",
"preferredLightLevel": 20
},
{
"code": "idle",
"priority": 0.9,
"minduration": 5000,
"maxduration": 30000,
"mincooldown": 2000,
"maxcooldown": 120000,
"priorityForCancel": 1.28,
"animation": "Sit"
},
{
"code": "lookaround",
"priority": 0.5
}
]
|
A lot of the properties here are quite self-explanatory, so we’re not going to cover all of them, but there are a few noteworthy points to be made.
As mentioned before, AI tasks are ordered by priority. This isn’t a requirement, it just makes things a bit easier to visualise. In this case, and similarly with many passive entities, the first two AI tasks are to flee from an entity on damage, and to get out of water. The other AI tasks are slightly less necessary, but help make the entity feel more alive.
In the ‘sleep’ AI task, there’s a property called “whenNotInEmotionState”. Emotion states are used quite commonly in AI tasks, but for the sake of the tutorial they have been left out. Many creatures use emotion states, so perhaps this could be a stretch goal for further entities you wish to create.
In the ‘flee entity’ AI task, there exists a “seekingRange” property. If an AI task uses other entities, you can often specify the range at which they have to be for the task to take place (or stop, in the case of ‘stopOnNearbyEntityCodes’).
AI Tasks can get very complex - This is just a small amount of what is actually capable. For more complex AI tasks, take a look at some hostile creatures’ AIs.
Entity Spawning
Concept
- SpawnConditions must be placed in the server property
| Embarrassing Side Story |
|---|
| When creating this tutorial, I spent a good 15 minutes debugging why entity spawning wasn’t working to find out that I had put the spawn conditions underneath the server property, not inside of it. Don't do what I did. |
- Entities have two types of spawn conditions
- Worldgen spawn conditions control how an entity spawns when a chunk is generated.
- This type of spawn uses a certain number of rolls per generated chunk to test if an entity spawn.
- Runtime spawn conditions control how an entity spawns gradually throughout the runtime of the world.
- Runtime spawns happen each 500ms.
- If the runtime chance is met, the entity is not covered under a grace time, and there is not too many of the entity already spawned, then it is added as a ‘spawn state’.
- Each spawn state is then further analysed with a specific position. If a valid position is found, including matching spawn conditions, then the entity will spawn.
- Worldgen spawn conditions control how an entity spawns when a chunk is generated.
- SpawnConditions also contain a “climate” property which will control where entities can spawn based on climate settings such as chunk rainfall, temperature, forestry, and height-level.
- Some entities will specify climate properties inside the worldgen and runtime properties. While this is acceptable, it is more recommended to contain them in the climate tag.
- Some entities can spawn additionally other entities. Many vanilla entities have male and female types, which will spawn together using these ‘companions’ properties. This tutorial does not do this, but take a look at hare-male for an example.
Implementation
Inside the server properties, just below the behaviors, add in the following code block:
"spawnConditions": {
"climate": {
},
"worldgen": {
},
"runtime": {
}
}
Firstly, let’s look at the climate property.
| How does chunk climate work? |
|---|
| A simplified explanation of how chunk climate works is that each chunk has its own climate values, which specify temperature, rainfall, shrubbery, forestry, fertility, and more. These properties control what animals spawn, what trees spawn, the type of ground, and more. By using ranges of these values to specify what can spawn, there can be a much more versatile world generation system. |
Each climate value is between a range of 0 to 1.
For the purpose of this tutorial, and to show how mob spawning works, we’re going to set very large ranges for each of the climate settings. You can modify and test these at will. Keep in mind entities still won’t spawn until the worldgen and runtime properties are set.
These are the values for the climate property. These should be quite self-explanatory:
"climate": {
"minTemp": -5,
"maxTemp": 25,
"minRain": 0.1,
"maxRain": 0.9,
"minForestOrShrubs": 0.05,
"maxForest": 1,
"maxShrubs": 1
},
Due to the nature of not having distinct defined biomes in Vintage Story, these values can sometimes require a fair bit of tuning to try and get correct.
World Gen Spawns
Let’s add some worldgen spawns. This will control how the entity spawns when a chunk is initially generated.
Here’s the code:
"worldgen": {
"TriesPerChunk": {
"avg": 0.5,
"var": 0
},
"tryOnlySurface": true,
"groupSize": {
"dist": "uniform",
"avg": 3,
"var": 2
}
},
This section is actually quite simple. TriesPerChunk is a [NatFloat] that controls how many times we should try to spawn this entity per viable (matching the climate properties) chunk. The ‘var’ value is a formality, but this just means that there will always be a 50% chance to attempt a spawn in a newly generated chunk. Note that this value is usually significantly lower.
TryOnlySurface only tests if the spawn is on the surface. This prevents animals spawning underground and also helps with spawning efficiency.
The final property, groupSize, is a NatFloat that determines how many of the creature should spawn in a single group. This particular example will spawn anywhere between 1 and 5 creatures, with a uniform chance for each value
After filling in these values, the entity spawning can actually be tested. Due to the spawn conditions that we’ve given, any new chunks that develop are extremely likely to spawn hares. You should start seeing them everywhere.
Runtime Spawns
Spawns on world generation are fine, however most entities need a way to keep spawning over the period of a game, otherwise they’d be very easy to make extinct.
"runtime": {
"group": "neutral",
"tryOnlySurface": true,
"chance": 0.05,
"maxQuantity": 10,
"__comment": "Make them spawn away from artifical light so they don't spawn inside farmland",
"maxLightLevel": 6,
"lightLevelType": "onlyBlockLight",
"groupSize": {
"dist": "uniform",
"avg": 3,
"var": 2
}
}
Runtime spawns have a few more properties than worldgen spawns, so let's go over them one by one.
The ‘group’ property defines what type of entity this is. This, in turn, affects where/when the entity should spawn, and can also be used to control if the entity should spawn when a grace timer is active.
The options are “hostile”, “neutral”, or “passive”. Note that these options are simply for spawning mechanics, and have no impact on the entity AI.
‘TryOnlySurface’ is identical to the same property in worldgen spawns.
‘Chance’ is the chance of attempting a spawn each 0.5 seconds. In the example, this is set to 5%.
‘MaxQuantity’ is the maximum number of this entity that can be currently loaded per player.
‘MaxLightLevel’ and ‘LightLevelType’ add an additional lighting check for an entity to spawn. In this case, our entity will not spawn on any blocks with a non-sunlight block light greater than 6. The light level types that can be used are listed here: [Show API Docs Page for EnumLightLevelType].
‘GroupSize’ is identical to the same property in worldgen spawns.
Runtime spawns can often be hard to test, so it is recommended to turn off the worldgen spawns. You can do this by setting the ‘triesPerChunk’ in worldGen to 0, and it is also recommended to start a new world. Any instances of the new entity found in the wild will have spawned due to runtime conditions. You may have to stay in the same place for a short while to get them to spawn.
When the worldgen and runtime spawns are confirmed to be working, feel free to tweak the values and see what works. Entity spawning can be a difficult thing to fine tune, so looking at other already-existing entities can be a good starting point.
Conclusion
After two tutorials, you should hopefully now have a fully functional and spawning entity. We looked at how to create, render, animate an entity, add behaviors & AI tasks to, and spawn the entity. This is probably one of the longest tutorials on the wiki, so well done if you’ve managed all of this.
Next Steps
Entities are very complex. If you want to find out more, perhaps study the entity files that exist in the base game. There’s a lot of properties that haven’t been used in this tutorial.