diff --git a/content/docs/ar-SA/guides/npc-workings/npc-intro.mdx b/content/docs/ar-SA/guides/npc-workings/npc-intro.mdx new file mode 100644 index 00000000..c4c4d7a2 --- /dev/null +++ b/content/docs/ar-SA/guides/npc-workings/npc-intro.mdx @@ -0,0 +1,216 @@ +--- +title: NPC Introduction +description: A introduction and demystifier of NPCs. +authors: + - name: ChJees +--- + +# NPCs - What are they? + +**Definition: NPC** stands for **N**on-**P**layable **C**haracter. + +NPCs is what populate the world of Hytale, from the smallest bug to colossal dragons. They all run on the same software while their exterior is only the representation of what they are. + +There are also NPCs that can stretch on the definition; they are not really characters but they use the inner-workings of one to perform complicated behavior. An adventurous modder could use it to create intricate static mechanisms if they so feel inclined. + +In short, if it can move and reacts to the world it is a NPC. Or **Mob** if you like that term. + +## Basics + +A NPC has a **Role** from which its functionality comes from. Without one it can't do anything at all. This is where you will spend the grand majority of your time editing. From defining its appearance and max health to laying down intricate **Instructions**. + +The NPC goes through many **States** in its lifetime when it interacts and reacts with the world. From idling around to going into a panic or engaging in combat. **States** can be really simple to becoming really intricate branches of **Instructions**. + +## What is a role? + +You set what _properties_ a NPC has in a **Role** and this is where the actual logic rests in. Everything rests inside this JSON file. + +The **Role** JSON files must be located in this directory inside your mod: `.\NPC\Roles` + +It is vital to place your **Role** JSON files inside _**this**_ directory because of how Hytale load mod assets. It is not the same directory structure that Hytale has, so keep that in mind. Other asset types also abide to these strict rules. + +## What is a instruction? + +**Instructions** are the core of your NPC. They come in many forms but the main one you will be interacting with is the humble `Instructions` block in the JSON file. + +It goes from top to bottom when executing the laid down instructions, only ever stopping if the most nested instruction matches its **Sensor**. + +``` +Scenario: + The NPC's State is Idle + A Player is nearby holding red berries in their hand. + +Instructions + Any: We do some check regardless of State (Continue after this) + State: Farming + State: Idle + Player: Has Red Berries + State: Alerted + State: Panicked +``` + +In this example will it first check the first instruction which as the **Any** sensor. Since it always matches it will execute the **Actions** of it. But since the instruction is told to **Continue** even if it matches it will go to the next one. + +The next instruction has the **Sensor** of the type **State** which checks if the NPC is in the state of Farming. But since it isn't it fails to match and go to the next one. + +It found a match in the instruction with the **State** of "Idle"! Since it did it will try to execute either **Actions** or **Instructions** nested inside it. + +Since there is a nested **Instruction** it will try to execute that. It is a match since there is a player nearby holding red berries in their hand. + +You would think that it would simply go out of the `Player: Has Red Berries` instruction now since there is no way but up? Nope, it will keep executing that instruction until it no longer matches. You can make it **Continue** on its way after if you really want to though. + +### Addressing the elephant in the room + +Can't we have both **Actions** and **Instructions** inside a **Instruction**? No we can't, why you wonder? They are incompatible in how they are executed inside a instruction. Since **Actions** can be blocking which means that the **Instruction** they are in are executed several times until all actions inside it are no longer blocking. This will be gone in-depth in another page. + +## What is a sensor? + +The sensor is the way NPCs react to the world around them. Without them they will not be able to do anything interesting. A instruction with no sensor will always execute its actions regardless of intent. + +```json +"Sensor": { + "Type": "Player", + "Range": 8, + "Filters": [ + { + "Type": "ItemInHand", + "Items": ["Plant_Fruit_Berries_Red"] + } + ] +} +``` + +> _Mmm, a player has delicious berries within my range..._ + +## What is a action? + +Attacking, equipping, playing animations, make particle effects... The list goes on. Actions are what make NPCs feel alive. They can be of the **blocking** type like `Timeout` which lets you make your NPCs perform actions in a sequence which makes for interesting theatrics. + +```json +"ActionsBlocking": true, +"Actions":[ + { + "Type":"PlayAnimation", + "Animation":"Happy" + }, + { + "Type": "SpawnParticles", + "Offset": [0, 1, 0], + "ParticleSystem": "Hearts", + "TargetNodeName": "Head" + }, + { + "Type":"Timeout", + "Delay": [3.0, 3.0] + } +] +``` + +> _I am so happy my heart can't contain it!_ + +## But what about Body and Head motion? + +They will help your NPC move around in the world. Body motion tells the NPC how to move around with its assigned motion controller. Head motion will tell the NPC how to move its head around. + +For moving around the world will you interact with the body motion the most, meanwhile the head motion will be used for looking at things. + +The majority of these motion types require a **Target** to be acquired by a **Sensor** in order to be used. But some types like `Wander` for body motion doesn't require one. + +```json +"Sensor": { + "Type": "Player", + "Range": 8, + "Filters": [ + { + "Type": "ItemInHand", + "Items": ["Plant_Fruit_Berries_Red"] + } + ] +}, +"HeadMotion": { + "Type": "Watch" +}, +"BodyMotion":{ + "Type":"Seek", + "RelativeSpeed": 0.6, + "StopDistance":3, +} +``` + +> _That person with those berries makes me wanna seek them out for a closer look._ + +## So what about states? + +I know I avoided talking about **States** until now but its because at its _**core**_ are states just another system with its own action and sensor pair. The main differentiator is that Hytale has it tightly integrated with NPCs to the point where its optimized into just being numeric values being checked when matching sensors and setting states. + +Are they bad? No, they are in fact the best way to change how a NPC acts in certain situations. Like the standard **Idle** state which tells the NPC how to act when its not in combat or panicking for example. Make it wander around and seem... Alive instead of just being a stationary loot dispenser. + +```json +"Instructions":[ + { + "Sensor":{ + "Type":"State", + "State":"Idle" + }, + "Instructions":[ + { + "Sensor": { + "Type": "Player", + "Range": 8, + "Filters": [ + { + "Type": "ItemInHand", + "Items": ["Plant_Fruit_Berries_Red"] + } + ] + }, + "HeadMotion": { + "Type": "Watch" + }, + "BodyMotion":{ + "Type":"Seek", + "RelativeSpeed": 0.6, + "StopDistance":3, + } + }, + { + "Reference": "Component_Instruction_Intelligent_Idle_Motion_Wander" + } + ] + } +] +``` + +> _While I'm Idle, if a player with red berries in hand are around I will seek them out. Otherwise I will wander around._ + +You may wonder what **"Reference"** is at the last instruction, it will be covered in another page in detail. The very quick version is that it allows you to reuse parts of a JSON in many different files. + +## What to do now? + +Since you now have an inkling of what is in store for NPCs, the first thing you should do is to extract the **Server** and **Common** folders from the `Assets.zip` file from your Hytale installation. Exclude the `Prefabs` folder from **Server** since we won't need it. The main file we will be referencing a lot is the `Template_Livestock.json` since it contains a lot of useful tricks when it come to instructions. + +If you are modding for **Release** it can be found here: `%appdata%\Hytale\install\release\package\game\latest` + +If you are modding for **Pre-release** it can be found here: `%appdata%\Hytale\install\pre-release\package\game\latest` + +If you installed Hytale in custom directory then you can find Hytale from there instead. + +Doing this will make it significantly easier to mod and find other assets like animations, sound effects and particle effects. + +Keep in mind that we will be referencing the `Server\NPC\Roles` folder a lot during this guide so keep that folder bookmarked. + +## TL;DR + +NPCs are derived from **Role** JSON files. + +The **Role** JSON files _**must**_ be located in this directory inside your mod: `.\NPC\Roles` + +NPC roles can be found in the `Server\NPC\Roles` folder in the Hytale `Assets.zip` archive. + +**Roles** have **Instructions** with **Sensors, Sub-Instructions, Actions Body motions & Head motions** which tell them how to interact with the world. + +**Instructions** is an **Instruction** in itself. That is why you need to put a second **Instructions** inside it. + +A **Instruction** with no **Sensor** acts like a **Any** Sensor. + +**Actions** are all executed instantly unless **ActionsBlocking** is set to `true` inside the **Instruction**. diff --git a/content/docs/ar-SA/guides/npc-workings/npc-role.mdx b/content/docs/ar-SA/guides/npc-workings/npc-role.mdx new file mode 100644 index 00000000..903e9a86 --- /dev/null +++ b/content/docs/ar-SA/guides/npc-workings/npc-role.mdx @@ -0,0 +1,525 @@ +--- +title: NPC Roles +description: Step into your new role, Ms Fairy. +--- + +# Describing your role + +> _All the world's a stage, and all the men and women merely players: they have their exits and their entrances; and one man in his time plays many parts, his acts being seven ages._ +> +> **- Shakespeare (As You Like It)** + +![npc-guide](/assets/guides/tutorial_role_Shakespeare.jpg) + +Roles make up the core of a NPC in how it interacts with the world. It defines things like appearance, max health, equipped items, what it drops upon death, etc. at its most basic functionality. Think of this as defining how it looks and the statistics it has. + +Roles can be changed during the lifetime of an NPC, so the possibilities are well, not endless, but many in how your NPC becomes unique. + +## Types + +Roles come in three flavors: **Generic**, **Variant** & **Abstract**. + +Out of all three is **Generic** the default one. It is declared as is with no preprocessing needing to be done. What is preprocessing you wonder? In short it looks at the JSON file it is and see if anything needs to be changed before Hytale reads it for its data. + +It is rarely used outside of testing because **Templates** are a widely used practice, because it saves time and let you reuse data and instructions across several roles with just a simple reference. The most reused templates can be found in the `Server\NPC\Roles\_Core\Templates` folder. + +**Variant** and **Abstract** are tied together because one type relies on the other in order to do its preprocessing magic. The short version is that the **Variant** role copies the data from the **Abstract** role and then overwrite it withs own on top of that. A example will help visualize it. + +> **Template_Shape.json file** + +```json +{ + "Type":"Abstract", + "Appearance": { "Compute": "Appearance" }, + "MaxHealth": { "Compute": "MaxHealth" }, + "KnockbackScale": 0.5, + "Parameters":{ + "Appearance":{ + "Value":"Point", + "Description":"Model to be used" + }, + "MaxHealth":{ + "Value": 10, + "Description":"Max health for the NPC" + } + } +} +``` + +> **Cube.json file** + +```json +{ + "Type":"Variant", + "Reference":"Template_Shape", + "Modify":{ + "Appearance": "Cube", + "MaxHealth": 30 + } +} +``` + +Variant **Cube** inherits from the Abstract **Template_Shape** and creates JSON file that looks like this in memory after all preprocessing is done: + +> **Cube role JSON in memory** + +```json +{ + "Type":"Variant", + "Reference":"Template_Shape", + "Appearance": "Cube", + "MaxHealth":30, + "KnockbackScale": 0.5 +} +``` + +While this example has a incomplete role it helps to illustrate how role **Types** work for roles at a high level. + +## Parameters and modifying them + +You may noted that in the previous example was the fields **Parameters** and **Modify**. They are used with the preprocessor to help to both make **Abstract** roles modifiable in their **Variant** roles, they are also used in other contexts when it come to making NPCs. Such as passing data to **Components** (Not to be confused with components in the **ECS**) + +We can set the parameters easily in our role JSON file. + +```json +"Parameters":{ + "ParameterName1":{ + "Value":"Apples", + "Description":"Put a short description that describes this parameter well. In this case a string." + }, + "ParameterName2":{ + "Value": 15, + "Description":"Numbers work well, integers and floating points are all good." + }, + "ParameterName3":{ + "Value": ["Food_Pie_Apple", "Food_Kebab_Vegetable"], + "Description":"You can store arrays of strings." + } +} +``` + +Also easily modify them in another role JSON file that references our template: + +```json +"Modify": { + "ParameterName1": "Oranges", + "ParameterName2": 42.67, + "ParameterName3": ["Rock_Stone"] +} +``` + +## Compute or more preprocessing + +This **Compute** thing has appeared several times now but never truly been addressed, but it will be now. + +```json +{ "Compute": "ParameterName" } +``` + +This wonderful tool can be used for more than just modifying parameters in a Role, in fact its vital when creating **Instructions** in order to ensure that you can correctly pass data to them in a readable way. Instead of using [magic numbers](https://en.wikipedia.org/wiki/Magic_number_(programming)) you can just pass the parameter through a **Compute** preprocessor. + +At its most basic level all it does is pass a value from your **Parameters** but it can do much more. Like math for instance. + +```json +"Parameters":{ + "First":{ + "Value":2, + "Description":"First number." + }, + "Second":{ + "Value": 3, + "Description":"Second number." + } +}, +"MaxHealth": { "Compute": "First + Second"} +``` + +It supports the most common mathematical operations like addition, subtraction, multiplication and division. + +- `+` : Addition **Example:** left + right +- `-` : Subtraction **Example:** left - right +- `*` : Multiplication **Example:** left \* right +- `/` : Division **Example:** left / right + +**Compute** also supports more advanced operations like negating values and exponents: + +- `-` : Negates the numeric value after it. **Example:** 10 + -10 = 0 +- `E` : Scientific notation for large numbers. **Example:** 5**E**5 = 500,000 +- `%` : Remainder, also known as modulus. **Example:** 35 % 32 = 3 +- `**` : Exponent like 10² which would give you 100. **Example:** 10\*\*2 = 100 + +You can also use open brackets like `( )` to make more complicated computation. **Example:** MaxHealth + (MaxHealth / 2) + +Square brackets `[ ]` are used for making arrays. Like string and numeric arrays in **Compute**. But the usage is quite limited at the moment. + +- **[0, 1, 2]** : Numeric array +- **["A", "B", "C"]** : String array + +Comparing things can also be done. Useful for setting a value in the **"Enabled"** field in a **Instruction** for example. + +- `==` : Equals, if the left and right side are equal it is **true**. **Example:** left == right +- `!=` : Not equal, if the left and right side are not equal it is **true**. **Example:** left != right +- `>` : Greater than, if the left side is greater than the right side it is **true**. **Example:** left > right +- `>=` : Greater than or equals, if the left side is greater than or equals to the right side it is **true**. **Example:** left >= right +- `\<` : Lesser than, if the left side is lesser than the right side it is **true**. **Example:** left \< right +- `\<=` : Lesser than or equals, if the left side is lesser than or equals to the right side it is **true**. **Example:** left \<= right + +There are other functions with **Compute** too that Hytale added in order to make their JSON preprocessing much easier. Here are all of them found so far: + +- **true** : Boolean constant for true. +- **false** : Boolean constant for false. +- **PI** : The value of [pi](https://en.wikipedia.org/wiki/Pi). +- **max**( _first, second_ ) : Puts the biggest of the two numbers in the **Compute**. +- **min**( _first, second_ ) : Puts the smallest of the two numbers in the **Compute**. +- **isEmpty**( _argument_ ) : Checks if the provided string or **Parameter** is empty. +- **isEmptyStringArray**( _argument_ ) : Checks if the provided string array or **Parameter** is empty. +- **isEmptyNumberArray**( _argument_ ) : Checks if the provided number array or **Parameter** is empty. +- **random**() : Provides a random number between 0.0 and 1.0. +- **randomInRange**( _first, second_ ) : Provides a random number between _first_ and _second_ arguments. +- **makeRange**( _argument_ ) : Makes a number array with two values that are the same as the _argument_. + +**Note:** It is possible to extend the **Compute** functions in other ways but this won't be addressed here. + +## Motion controllers + +Motion controllers tell the NPC how to move around in its **Role**. Roles that walk on the ground use the **Walk** controller. A flying **Role** use the **Fly** controller. Meanwhile aquatic **Role**'s use the **Dive** controller. + +The motion controllers are used in conjunction with **"BodyMotion"** inside instructions in order to make the NPCs move around. But keep in mind that they are still affected by gravity and other external forces even without input from the instructions. + +In its current implementation in Hytale are multiplie motion controllers ill-advised because of how buggy it is. Even reference in the `Template_Birds_Passive.json` file that they need to hack it in order to make flying roles walk on the ground. + +> **Example from Template_Kweebec_Sapling.json** + +```json +"MotionControllerList": [ + { + "Type": "Walk", + "MaxWalkSpeed": { "Compute": "MaxSpeed" }, + "Gravity": 10, + "RunThreshold": 0.3, + "MaxFallSpeed": 15, + "MaxRotationSpeed": 360, + "Acceleration": 10 + } +] +``` + +## Instructions + +With the skeleton of a **Role** being explained we now come to the meat of it. The beating _heart_ of a NPC that let it do truly wondrous things. + +As was said in the introduction, are instructions executed from top to bottom. Stopping at the first deepest nested instruction whose **Sensor** that match. + +> **Taken from the introduction page** + +```json +"Instructions":[ + { + "Sensor":{ + "Type":"State", + "State":"Idle" + }, + "Instructions":[ + { + "Sensor": { + "Type": "Player", + "Range": 8, + "Filters": [ + { + "Type": "ItemInHand", + "Items": ["Plant_Fruit_Berries_Red"] + } + ] + }, + "HeadMotion": { + "Type": "Watch" + }, + "BodyMotion":{ + "Type":"Seek", + "RelativeSpeed": 0.6, + "StopDistance":3, + } + }, + { + "Reference": "Component_Instruction_Intelligent_Idle_Motion_Wander" + } + ] + } +] +``` + +This is an example of a **2 deep** nested instructions. It goes from the **"Instructions"** from the role and then iterates through its only instruction it has. If the **State** of the NPC is `Idle` it _**matches**_ and will try to either execute **Actions** or **Instructions**. Never both at the same time. + +Assuming that the NPC **State** is `Idle` it _**matches**_ and will iterate through the instructions from that instruction. + +> **First match** + +```json +"Instructions":[ + { + "Sensor": { + "Type": "Player", + "Range": 8, + "Filters": [ + { + "Type": "ItemInHand", + "Items": ["Plant_Fruit_Berries_Red"] + } + ] + }, + "HeadMotion": { + "Type": "Watch" + }, + "BodyMotion":{ + "Type":"Seek", + "RelativeSpeed": 0.6, + "StopDistance":3, + } + }, + { + "Reference": "Component_Instruction_Intelligent_Idle_Motion_Wander" + } +] +``` + +It looks for a Player which holds red berries in hand but it can't find any in the first instruction which makes it NOT _**match**_. So it tries the next one which is a reference to a **Component** that makes the NPC wander around in a idle manner. + +> **Final match** + +```json +{ + "Reference": "Component_Instruction_Intelligent_Idle_Motion_Wander" +} +``` + +If nothing changes the NPC will continue executing this instruction until the first instruction matches. + +### Continue Vs. TreeMode + +The main difference between `"Continue"` and `"TreeMode"` in a instruction is how it handles finding matches. + +With `"Continue"` set to **true** the instruction executing it will continue executing instructions despite finding a match in this instruction. It is useful for running logic in the background and perform reusable logic in a `Component`. + +With `"TreeMode"` set to **true** the instruction executing it will continue after all instructions inside this instruction fail to match. Confusing? Thought so. It acts like a behavior tree where it will lock onto the first matching instruction and keep executing it until it no longer matches. + +If you set `"InvertTreeModeResult"` to **true** in a instruction nested inside the `"TreeMode"` instruction will invert its behavior. + +> **Example tree** + +```json +"Instructions":[ + { + "TreeMode":true, + "Instructions":[ + { + "Sensor":{ + "Type": "Player", + "Range": 8, + "Filters": [ + { + "Type": "ItemInHand", + "Items": ["Plant_Fruit_Berries_Red"] + } + ] + }, + "Actions":[ + { + "Type": "SpawnParticles", + "Offset": [0, 1, 0], + "ParticleSystem": "Hearts", + "TargetNodeName": "Head" + } + ] + }, + { + "Sensor":{ + "Type": "Player", + "Range": 8, + "Filters": [ + { + "Type": "ItemInHand", + "Items": ["Food_Pie_Apple"] + } + ] + }, + "Actions":[ + { + "Type": "SpawnParticles", + "Offset": [0, 1, 0], + "ParticleSystem": "Stunned", + "TargetNodeName": "Head" + } + ] + } + ] + }, + { + "Sensor":{ + "Type":"Any" + }, + "Actions":[ + { + "Type": "SpawnParticles", + "Offset": [0, 1, 0], + "ParticleSystem": "Question", + "TargetNodeName": "Head" + } + ] + } +] +``` + +### Template_Livestock.json is your bible + +Why should you care about this file so much? It is because it contains all the _tricks_ and good instruction _patterns_ that you will need to make interesting NPCs with your **Roles**. + +It is located in: `Server\NPC\Roles\_Core\Templates` + +For example if you look at the `"InteractionInstructions"` in the file will you see a reusable tree structure for handling different cases where the player interacts with the NPC. It is easy to modify and get to work in your own Role JSON files. + +It also shows a good usage of the `"Enabled"` property in instructions which leads to highly modular templates that can be reused for _**many**_ **Roles**. + +**Note:** For programmers is the `NPCPlugin` class your bible _**and**_ documentation for NPCs. **Class path:** `com.hypixel.hytale.server.npc.NPCPlugin` + +### Main instructions + +In the **Role** `"Instructions"` is where the NPC truly comes alive. It is run every in-game tick where it executes instructions as far as it can. + +This is a good place to put "scripts" outside states which is run every tick. Let you reset things like _Alarms_ and do **Flock** logic. + +Using our previous example we can make our NPC emit hearts every 2 to 3 seconds. + +To do that we first need to setup a **Timer** in our instructions. To do that we first make a instruction with a `Any` **Sensor** that is set to run only **Once**. Make sure to set `"Continue"` to **true** in it to ensure instructions after it can execute despite this always matching. + +After that we implement our second instruction which checks if the **Timer** that we started has stopped with the `Timer` **Sensor**. It matches if the **Timer** has stopped and will execute its actions that spawns heart particles atop the NPCs head and restarts the timer. + +> **Modified example from the introduction** + +```json +"Parameters":{ + "HeartTimer":{ + "Value":"HeartEmitterTimer", + "Description":"The name for the timer." + } +}, +"Instructions":[ + { + "$Comment":"Initialize our Timer.", + "Continue":true, + "Sensor":{ + "Type":"Any", + "Once":true + }, + "Actions":[ + { + "Type": "TimerStart", + "Name": { "Compute": "HeartTimer" }, + "StartValueRange": [0.1, 0.1], + "RestartValueRange": [2, 3] + } + ] + }, + { + "$Comment":"Let us check if our heart timer is finished.", + "Continue":true, + "Sensor":{ + "Type":"Timer", + "Name": { "Compute": "HeartTimer" }, + "State":"Stopped" + }, + "Actions":[ + { + "Type": "SpawnParticles", + "Offset": [0, 1, 0], + "ParticleSystem": "Hearts", + "TargetNodeName": "Head" + }, + { + "Type":"TimerRestart", + "Name": { "Compute": "HeartTimer" } + } + ] + }, + { + "Sensor":{ + "Type":"State", + "State":"Idle" + }, + "Instructions":[ + { + "Sensor": { + "Type": "Player", + "Range": 8, + "Filters": [ + { + "Type": "ItemInHand", + "Items": ["Plant_Fruit_Berries_Red"] + } + ] + }, + "HeadMotion": { + "Type": "Watch" + }, + "BodyMotion":{ + "Type":"Seek", + "RelativeSpeed": 0.6, + "StopDistance":3, + } + }, + { + "Reference": "Component_Instruction_Intelligent_Idle_Motion_Wander" + } + ] + } +] +``` + +### Interaction instructions + +In the **Role** `"InteractionInstruction"` is where it control how the NPC can interact with players. The interaction distance is generous so you can limit it by checking how far the matching player is. + +The theory is that you first run a check whether the player can interact with it, if it can't you set it as not interactable. If you can interact with the NPC then you set a hint message and make it interactable. + +Once it detects activation you run whatever actions you desire. + +You can find a reference in the `Kweebec_Merchant.json` for how it works. + +> **Simple interaction logic** + +- Check if NPC is not interactable. +- Set as interactable with hint. +- Execute actions upon interaction. + +For more advanced interaction logic consult the `Template_Livestock.json` file. It utilizes `"TreeMode"` on each scenario set to **true** to perform many interaction checks before finally setting the NPC as not interactable. + +> **Advanced interaction logic** + +- For each instruction with `"TreeMode"` set to to **true**. + - Check if NPC is interactable. + - Set as interactable with hint. + - Execute actions upon interaction. +- Set as not interactable. + +**Note:** It can't override **blocking** actions in the main `"Instructions"`. + +### Death instruction + +In the **Role** `"DeathInstruction"` is where a single instruction will be run upon NPC death. It should be kept short to tie up any loose ends or do something as silly as spawn a mouse upon death. Otherwise it works like a normal instruction where it can have nested instructions. + +## Good practices + +Use **Parameters** when possible in the **Role** file. Can be ignored for single use cases where the same piece of data won't appear again. + +Use **Compute** wherever possible for both readability and being able to easily change parameters from one place while testing your NPC **Role**. + +Try to use _cheap_ **Sensors** when possible to avoid lagging the server. + +Learn and understand the versatility of **States** because that is the main building block of your NPC **Role**. This is a topic for another page in itself. + +When you repeat the same piece of instructions, actions or sensors many times it may be worth looking into **Component** JSON files. But that is a topic for another page. + +## What now? + +Test a lot. Figure out what makes a NPC tick through its role. Look at existing roles made by the Hytale developers to see how they did it. + +Explore how combat works with the interaction system. diff --git a/content/docs/ar-SA/guides/npc-workings/npc-states.mdx b/content/docs/ar-SA/guides/npc-workings/npc-states.mdx new file mode 100644 index 00000000..1ce62f26 --- /dev/null +++ b/content/docs/ar-SA/guides/npc-workings/npc-states.mdx @@ -0,0 +1,429 @@ +--- +title: NPC States +description: The state of matter, or in this case the state of NPCs. +--- + +![npc-guide](/assets/guides/tutorial_states0.svg) + +# States + +You are in a state now even if you don't know it. You are now `Reading` this right? While reading this you occasionally take a `.Sip` from your trusty cup of coffee. + +What does this have to do with **States** you may wonder? Like your life a NPC goes through various states during its lifecycle in Hytale. + +## What is a state? + +In short is a **State** a way of keeping track of what a NPC is doing without requiring setting and reading numbers, instead Hytale does all that _work_ for you by translating [human readable](https://en.wikipedia.org/wiki/Human-readable_medium_and_data) text into numbers. This creates a highly efficient system that is a lot less taxing for the game engine to deal with. + +The practical usage of **States** is that it makes writing and reading the **Instructions** of a NPC easier. The core of a **State** starts by placing down a **Sensor** with the name of it. + +```json +"Sensor":{ + "Type":"State", + "State":"Idle" +} +``` + +You can make as many **States** as you want but _**REMEMBER**_ they all must be **LINKED** together in some way from the **StartState** state. Otherwise your **Role** fails to validate in your JSON file and you will become very sad. + +Using the example above this **State** will work if you set `"StartState"` to `"Idle"` in your **Role**. If its not set the default state will be: `"start"`, so make sure to set it to a state that makes sense like `"Idle"`. + +```json +{ + "StartState":"Idle", + "Instructions":[ + { + "Instructions":[ + { + "Sensor":{ + "Type":"State", + "State":"Idle" + }, + "Actions":[ + { + "Type":"Nothing" + } + ] + } + ] + } + ] +} +``` + +How does this work? Let's map this to a table with **State transitions**. + +| Source | From | To | Trigger | +| :------: | :------------: | :----: | --------- | +| **Role** | `"StartState"` | `Idle` | **Start** | + +This is valid because the `Idle` is referenced by the `"StartState"` from the **Role**, thus creating a link. Remember there cannot be any _stray_ states. + +Its great and all, but it lacks transitions to other states. Here is another example with valid transitions between states. + +```json +{ + "StartState":"Idle", + "Instructions":[ + { + "Instructions":[ + { + "Sensor":{ + "Type":"State", + "State":"Idle" + }, + "Instructions":[ + { + "Sensor":{ + "Type": "Player", + "Range": { "Compute": "ViewRange" }, + "Filters": [ + { + "Type": "LineOfSight" + }, + { + "Type": "ViewSector", + "ViewSector": 180 + }, + { + "Type": "ItemInHand", + "Items": ["Rock_Stone"] + } + ] + }, + "Actions":[ + { + "Type": "SpawnParticles", + "Offset": [0, 1, 0], + "ParticleSystem": "Hearts_Subtle" + }, + { + "Type":"State", + "State":"Work" + } + ] + } + ] + }, + { + "Sensor":{ + "Type":"State", + "State":"Work" + }, + "Instructions":[ + { + "Sensor":{ + "Type": "Player", + "Range": { "Compute": "ViewRange" }, + "Filters": [ + { + "Type": "LineOfSight" + }, + { + "Type": "ViewSector", + "ViewSector": 180 + }, + { + "Type": "ItemInHand", + "Items": ["Rock_Stone_Cobble"] + } + ] + }, + "Actions":[ + { + "Type": "SpawnParticles", + "Offset": [0, 1, 0], + "ParticleSystem": "Alerted" + }, + { + "Type":"State", + "State":"Idle" + } + ] + } + ] + } + ] + } + ] +} +``` + +The state transitions will look like this with this: + +| Source | From | To | Trigger | +| :---------: | :------------: | :----: | ------------------------------------------- | +| **Role** | `"StartState"` | `Idle` | **Start** | +| Instruction | `Idle` | `Work` | Player with item in hand: Rock_Stone | +| Instruction | `Work` | `Idle` | Player with item in hand: Rock_Stone_Cobble | + +As you can see from these transitions this makes a complete chain of states where no state is hanging loose. You may have noticed that we use a **Action** of the type `"State"` there, that is how we switch between states in NPCs. + +```json +{ + "Type":"State", + "State":"Work" +} +``` + +The **Sensor** and **Action** `"State"` are interlinked, and Hytale makes sure of that. A lot goes on in the background to ensure NPCs will not infinitely get stuck in a **State**. Hytale outright refuses you to add or update **Roles** if **States** aren't linked. + +## Sub-states + +The **Sub-state** is like a **State** but its a smaller part of one. By default you enter one even if you haven't defined it. For example if you just go into the **State** of `"Idle"` without defining its **Sub-state** you in fact enter the `"Idle.Default"` **State**. Its more of a convenience feature instead of having to type it in every time you just want to enter the `"Idle"` **State**. + +But the main feature of **Sub-states** is that it let you partition states into different tasks you want a NPC to perform. For example you can make a Job driver where the NPC does work and it switches states until it exits it and goes back to being `"Idle"`. + +Within a **State** can you set or check what **Sub-state** it has by simply putting `.SubStateName` as its `State`. It is the `.` in the name that determines that it's a `Sub-state` its setting or checking. + +> **Checking for sub-state** + +```json +{ + "Sensor":{ + "Type":"State", + "State":".SubStateName" + } +} +``` + +> **Setting a sub-state** + +```json +{ + "Actions":[ + { + "Type":"State", + "State":".SubStateName" + } + ] +} +``` + +_In the example below the details will be omitted for clarity._ + +```json +{ + "StartState":"Idle", + "Instructions":[ + { + "Instructions":[ + { + "$Comment":"Timer initialization.", + "Sensor":{ + "Type":"Any", + "Once":true + } + }, + { + "$Comment":"Work timer is triggered and we will check if we can do farm work. If we can go to the Farm state.", + "Continue":true, + "Actions":[ + { + "Type":"State", + "State":"Farm.Goto" + } + ] + }, + { + "Sensor":{ + "Type":"State", + "State":"Idle" + } + }, + { + "$Comment":"Main state of 'Farm' where our logic lies in.", + "Sensor":{ + "Type":"State", + "State":"Farm.Goto" + }, + "Instructions":[ + { + "$Comment":"Perform checks to see if our Farming state fails, and if it does go back to being Idle.", + "Continue":true + }, + { + "$Comment":"Go to our designated farming area.", + "Sensor":{ + "Type":"State", + "State":".Goto" + }, + "Instructions":[ + { + "$Comment":"Reached our destination. Switch to '.Jobdriver' sub-state.", + "Actions":[ + { + "Type":"State", + "State":".Jobdriver" + } + ] + } + ] + }, + { + "$Comment":"Perform farming job until we no longe can.", + "Sensor":{ + "Type":"State", + "State":".Jobdriver" + } + } + ] + } + ] + } + ] +} +``` + +A example transition table could look this: + +| Source | From | To | Trigger | +| :---------: | :------------: | :--------------: | -------------------------------------------------- | +| **Role** | `"StartState"` | `Idle` | **Start** | +| Instruction | `Idle` | `Farm.Goto` | Timer for checking Work triggered and we can farm. | +| Instruction | `Farm.Goto` | `Farm.Jobdriver` | NPC reached target destination. | +| Instruction | `Farm` | `Idle` | No more farming work to be done. | + +Notice how we just went to the `Farm.Goto` **State** immediatly instead of first going to the `Farm` **State**? That is a feature of **Sub-states**. It let you do general logic inside a **State** while doing more fine-grained logic inside a **Sub-state**. + +With clever use of **Sub-states** will writing **Instructions** be a lot more enjoyable. + +**Note:** `"$Comment"` will be ignored by Hytale when reading the JSON file. Its used for commenting instructions and other things. There is also `"$Todo"` for the same purpose. + +## Parent state and JSON components + +If you ever looked into Hytale **Component** files then you will notice they don't use `"State"` **Actions** when setting state. That is because **Components** are special cases. You will need to instead use the type `"ParentState"` to set a **State** from a **Component**. + +> **Example from: Component_Instruction_Damage_Check.json** + +```json +"Parameters": { + "_ImportStates": [ "Chase", "Search" ] +} +``` + +```json +{ + "Type": "ParentState", + "State": "Search" +} +``` + +The technical bits is that it uses the `"_ImportStates"` parameter in the **Component** parameters. The actual **State** names you define in the `"_ImportStates"` parameter are aliases or stand-ins for the actual **States** being passed in from the _**outside**_. That way you can reuse the **Component** in many **Roles** which all can have different **State** names. + +In order to input the stand-in states for the real states you need to **Modify** them by setting the `"_ExportStates"` in the **Component** you **Reference** to inside the **Instruction\Action\Sensor**. You need to to input the **States** in the same order as the `"_ImportStates"` define it in the **Component** in order for it to work. The example below helps illustrate how it works. + +> **Example from: Template_Livestock.json** + +```json +{ + "Reference": "Component_Instruction_Damage_Check", + "Modify": { + "_ExportStates": [ "Flee.Switch", "Panic" ], + "AlertedRange": { "Compute": "AlertedRange" } + } +} +``` + +## State transitions + +In a **Role** can you peform **Actions** between **States**. It is useful for doing things like clearing animations or other utilitarian tasks. CPU intensive **Actions** should be frowned upon because switching **States** may happen fast. + +The `"StateTransitions"` property contains conditions and **Actions** to perform _between_ **State** transitions. + +> **Example from: Template_Livestock.json** + +```json +{ + "StateTransitions":[ + { + "$Comment": "Always clear target and reset instructions when going back to idle.", + "States": [ + { + "To": [ "Idle" ], + "From": [ ] + } + ], + "Actions": [ + { + "Type": "ReleaseTarget" + }, + { + "Type": "ResetInstructions" + }, + { + "Type": "PlayAnimation", + "Slot": "Status" + } + ] + } + ] +} +``` + +You may have noticed that `"From"` in `"States"` is empty. That means **from any State**. The same applies to `"To"`. You can define more than one **State** in each if you want to. Like going _from_ `Idle` _to_ your various jobs like `Farm` & `Mine`. + +> **Valid configuration** + +```json +"States": [ + { + "From": [ "Idle" ], + "To": [ ] + } +] +``` + +> **Also valid configuration** + +```json +"States": [ + { + "From": [ ], + "To": [ "Idle" ] + } +] +``` + +## Tricks with states + +Using all this knowledge you can do some neat tricks with **States**. You can use **States** as a intermediary evaluator with just the tools provided by Hytale for example. By setting them as a **Sub-state** inside your `Idle` **State** can you cycle through the **Sub-states** in order to do checks when combined with `"InteractionInstructions"` and blocking **Actions**. Hytale does not yet allow you to cancel a blocking **Action** when its running from the outside, so this is where this trick comes in place. + +### Case study + +You can study my old **Role** I used for my Little Helpers mod for insights on how it was accomplished. A good starting point is to look for the `.AskTask1` **Sub-state** where the cycle starts. This is before I implemented my own Java **Plugin** code to ease this kind of logic. + +**Role JSON:** [Template_Fairy.json](/assets/guides/Template_Fairy.json) + +> **From Template_Fairy.json** +> +> In `Idle` **State** + +```json +"Sensor": { + "Type": "State", + "State": ".AskTask1" +} +``` + +> In `"InteractionInstructions"` + +```json +"Sensor": { + "Type":"HasInteracted" +}, +"Actions": [ + { + "Type":"State", + "State":"Idle.AskTask1" + } +] +``` + +**Note:** This won't work in Hytale as is because it uses **Plugin** specific **Action & Sensors**. And it uses **Components** that aren't from Hytale either. + +## Limitations + +You can't check for **Sub-states** inside a `"InteractionInstruction"`, so you have to type out the full **State** name with **Sub-state** included. + +While **States** are powerful in their simplicity, they don't allow for sub-**Sub-states**. To achieve that you would want to use the **Flag** system with its own **Action** and **Sensor**. diff --git a/content/docs/ar-SA/guides/plugin/Interactable-NPCs.mdx b/content/docs/ar-SA/guides/plugin/Interactable-NPCs.mdx index a66b0e63..7c6b89d0 100644 --- a/content/docs/ar-SA/guides/plugin/Interactable-NPCs.mdx +++ b/content/docs/ar-SA/guides/plugin/Interactable-NPCs.mdx @@ -261,7 +261,7 @@ So all you have to do is extend that same API. To show what that looks like in practice, here’s a custom Kweebec Merchant who opens to a dialog box with 2 buttons: Browse Wares and Goodbye. This is a small Java plugin and one .ui layout file, using the exact same InteractionInstruction pipeline from this guide. -