This tutorial is designed to allow any newcomer, with basic programming knowledge, to master all the core principles and systems for scripting in Kingdom Come: Deliverance.
Warning: This tutorial DOES NOT cover how to create an actual functioning modification. As it relies on a newly created level completely independent from the main level (rataje.cry) it allows you to use all features, some of which are limited or completely non-modifiable when creating a modification for the vanilla game. Please follow the documented guidelines for what and how is modifiable.
The tutorial goes through 3 major phases
Basics principles of scripting (via basic NPC scripting)
More complex interactions between NPCs and an NPC and the world
Each exercise, marked by a Roman numeral, is or will be accompanied by a video counterpart which shows you how to actually do the exercise.
However, try to come up with you own solution first. Each exercise lists the nodes you will need. Later (= more complex) exercises DO NOT list the most basic nodes (like GraphSearch, Move, Sequence, Parallel, Success etc.) which are integral part of almost any script.
Each exercise will feature a link for downloading one(!) correct solution - a set of XML files with correct MBT trees, typically behavior definitions. Some scripts may have several possible good solutions often with different downsides and advantages over other. Take your time to explore alternative solution and their different characteristics. Still, even if you have the correct XML files you have to properly setup your level and entities and some basic logic (like behavior calling in the NPC brain). The videos will assist you with that every time.
Save the level and script trees often.
Each exercise ends with an implicit running of the game mode. Simply press Ctrl+G and you will be dropped to the level. Exiting the game mode is done via Shift+Esc.
The tutorial is also designed in such a way that you don’t have to discard the result of the previous exercise. The following exercise either expands on the previous one or creates something new from scratch and the results may coexist.
Goal: 2 behaviors. NPC walks in a loop. NPC keeps walking between random tagPoints. Both behaviors support save/load.
Add several tagPoints in the level and create looped path for the NPC to follow by creating links between the tagPoints
Create a new behavior on the SA, name it patrol:
Definition of patrol:
The NPC will find the initial tagPoint
Move to the initial tagPoint
Loop
If there is another tagPoint linked from the previous tagPoint, go to it
Wait at each point for several seconds
Edit the NPC’s subbrain so that it calls the patrol behavior. Test the behavior.
Add Save/Load Support to the patrol behavior:
Info: Saves are created using Ctrl+F5. All AI trees are restarted upon loading so it your responsibility to reconstruct them properly. The only variables which are saved are Brain-scope variables (defined in Brain, accessible from all subbrains) and Persitent variables (that's a variable property, false by default). The rest is reinitialezed upon loading. Links, unless specified otherwise in LinkTagDefinitions.xml, are persistent
You will need at least one persistent variable, you will need some IfCondition tests, and you must pay attention to the order of execution of GraphSearch and Move nodes.
Create a new behavior on the SA, name it randomPatrol
Definition of randomPatrol:
The NPC finds a random tagPoint via GraphSearch (it must not be the point where the NPC is standing now)
The NPC goes to the random tagPoint, waits there for a few seconds
Loop it
Create links between the NPC and each tagPoint. You can add a new set of tagPoints or you can reuse the same points used by patrol
Edit the NPC’s subbrain so that it calls the randomPatrol behavior. Test the behavior.
Add Save/Load Support to the randomPatrol behavior.
Newly introduced nodes:
IfCondition
Nodalyzer
NegationOp
WUIDFilter
VarOperation (PushBack)
Extras:
The patrol can be done in an alternative way. Try it.
The tagPoints are linked the same way. But instead of finding the next tagPoint at every step, store the whole loop in an array, and loop through that array. You will need a bit advanced GraphSearch setting. You can then loop through the array using Loop, For, ForEach, While. Try them all.
Notice: There is a danger you will either not load all the tagPoints or, since the tagPoints are looped, the GraphSearch might attempt to create an infinite array, which would lead to program freeze or crash. To prevent this either limit the GraphSearch depth search or create a test which prevents duplicate array members. The latter can be achieved many ways. One of them is a terminator self-link on the last object.
Try another solution for randomPatrol:
Find and store all tagPoints in an array. Then, in a loop, always select a random array member via RandomItem node or via NumericalOperation node using “rand” function.
Goal: NPCs select their behavior based on link data. One NPC executes patrol behavior. 2 other NPCs execute randomPatrol behavior and utilize a simple reservation system via dynamic links.
Create a new link definition in LinkTagDefinitions.xml. Define it as name work_ST which can carry data of the type string. (Note: New type definitions require the editor the be restarted)
Place another 2 NPCs in the level, name them test_ST_npc2 and test_ST_npc3
Give them souls of the same names
Give the NPCs the same brain as that of test_ST_npc1 (brain test_ST_npc_brain)
Create a link from the npc1 to the SA, and name it work_ST[(‘patrol’)]. Note: ‘patrol’ is the data here.
Create a links from the npc2 and npc3 to the SA, and name each work_ST[(‘randomPatrol’)].
Edit the npc subbrain definition so that:
An NPC finds the SA
Stores the link data in a variable
Calls a behavior of the name stored in the variable
Change the link networks required by both behavior so that the SA is the starting reference point, not he NPCs
Change the behaviors so that they have proper Origins in their GraphSearches (automatically created variable __area.id)
2 NPCs are now executing the randomPatrol behavior and thus are at risk of going to the same spot and clipping through one another. Create a simple reservation systems using link reserved. Change the definition of randomPatrol:
When searching for next tagPoint:
The NPC excludes the tagPoint it's standing on, and those which are reserved.
The NPC does a reservation cleanup - deletes any reserved link from the area to a tagPoint.
Creates a reserved link from the area to the tagPoint it found.
Goal: The NPC doing the patrol behavior reacts to player entering the SmartArea by inspecting the player's original position.
Create a new custom type in ..\Data\Libs\AI\TypeDefinitions.xml
name the type:subtype test_ST:intruder
with a sole member "intruder" of the type common:wuid
Note: Adding new types requires editor restart
Create a new mailbox template definition
Name it intruderST
Make it accept the new type test_ST:intruder
Add a brain to the SA
name the brain test_ST_sa
give it all the types of trees an SA-type brain can handle: OnUpdate/OnEnter/OnLeave/OnRequest/OnRelease
save the tree definitions in _test\test_ST_sa.xml (full path ..\Data\Libs\AI_test\test_ST_sa.xml)
Add the node Wait=-1 to onUpdate tree and Success node to other trees. They will fail if left empty.
SA’s onEnter tree definition:
when player entered the SA send a message test_ST:intruder with intruder variable carrying the WUID of the intruder (equal to global variable __player in this case) to an NPC doing the patrol behavior.
Edit the patrol behavior:
Add the new mailbox (intruderST) to this behavior
Create a new variable mode of the type string. Set the default value to “patrolling”. Mode “patrolling” will represent the old patrol logic from previous exercises. Value “inspecting” will represent the new mode, where NPC reacts to an intruder.
Create the “inspecting” logic:
Move to the intruder, run if the intruder is far
After the movement, test if the intruder is still close
If the intruder is close:
Play animation ADLG_Emphasis with tag "angry"
Then keep turning to the player for a period of time using TurnBody.
If the intruder is far:
Play animation LookingAround (no tags) for several seconds
Then set mode back to “patrolling”
Create a new logic for switching between the behavior modes:
Use a ProcessMessage or ReadMessage node to process the messages sent from the SA. Plug it in parallel to the rest of the logic. Don’t forget a Loop; you need to be able to catch and process multiple messages. The reaction to the message is a change of the mode to “inspecting”
Using ContinuousSwitch, create a logic which can react to a change in mode variable and switch to the proper mode tree.
When returning to “patrolling” the NPC must find the currently nearest TagPoint and continue patrolling from there.
Newly introduced nodes:
SendMessageToNPC
InstantSendMessageToNPC
MultiSendMessageToNPC
InstantMultiSendMessageToNPC
ReadMessage
ProcessMessage
ContinuousSwitch
Switch
DistanceCondition
Selector
Fail
AnimationEndWait
StopAnimation
TurnBody
CategoryFilter
RangeSorter
Extras:
Change the logic so that the NPC goes to the nearest tagPoint when switching back to “patrolling” mode
Goal: 2 NPCs placed in an area keep synchronizing and wave at each other at the same time in random intervals. Other entities can enter and leave the area and the 2 NPCs react to these events.
Create a regular square-shaped triggerArea
Create 2 new NPCs test_ST_waveMaster and test_ST_waveSlave. Place them inside the triggerArea.
They will call behaviors named waveMaster and waveSlave (resp.) from the SmartArea.
Add 2 tag points for each NPC - wavePoint and hidePoint
Place the points inside the triggerArea
The wavePoints should be placed about 2 meters apart and must be facing one another
Add a new behavior to the SA, name it waveMaster. Defintion of the behavior:
Find the two points - wavePoint and hidePoint
Feature a variable mode with 2 possible values "wave" and "hide" and create a control structure which reacts to changes in mode value (ContinuousSwitch)
hide mode definition:
Go to hidePoint and wait
wave mode definition:
Move to wavepoint and align with it
Loop
Random Wait between loop repetitions
Wait for synchronization. Synchronize node settings:
LockManagerType = Local
LockCount = 2
When Synchronized, play animation GreetingsUpperBody with tag waveSmall
Have a parallel control script which changes the mode values:
Looped ExternalLock stalkerEntered (LockmanagerType=Local). When open, set mode="hide" and lock the ExternalLock.
Looped ExternalLock stalkerLeft (LockmanagerType=Local). When open, set mode="wave" and lock the ExternalLock.
Add new behavior to the SA, name it waveSlave. The defition is the same as waveMaster with some exceptions:
The NPC uses its own pair of points wavePoint and hidePoint
The wave mode lacks random delays between loop repetitions
ExternalLocks are named playerEntered and playerLeft
Create a new NPC and name it test_ST_stalker. Place it outside the triggerArea
He will call a behavior called stalker from the SmartArea.
Create 2 points for the stalker NPC, which will be used by the behavior.
Place them outside the triggerArea
Direct trajectory between the points must run across the triggerArea
Add a new behavior to the SA, name it stalker. The NPC will be crossing the triggArea in random intervals. Definition of the behavior:
Find the 2 stalker points
Go between them in a loop
At each stop, play the animation LookingAround for random amount of time
Expand the unused onUpdate tree of SmartArea and turn into an area-intruder observing system:
Find the triggerArea
Store player and stalker NPC in an array
Using AreaPresence node create a script which unlocks the ExternalLocks in waveMaster or waveSlave behaviors when the stalker NPC or player enters or leaves the triggerArea.
Newly introduced nodes:
Synchronize
ExternalLock
SetExternalLock
AreaPresence
ExactMove
Extras:
Make the whole script save/load proof
Add a second waveSlave NPC and using Sempahore ensure that out of the 3 wave NPCs only 2 at max can synchronize and play the animation.
Instead of aligning with the wavePoints in the waveSlave and waveMaster behaviors prior to synchronization, ensure that the NPCs turn to the NPC they Synchronize with, and that the waveSlave NPC turns to the master only after synchronization.
Add another stalker NPC and change the logic of the SmartArea's onUpdate tree so that the master only goes to hiding when both stalkers are inside the area.
Goal: NPCs picks and drops an npc tool, then picks and pockets an apple, a shield, a sword which it also equips, and a book. Then the NPC proceeds to read the book, and observes if the player dropped any item. When that happens the NPC closes the book, draws a weapon, picks the item, and then returns back to reading.
Place 5 itemSlots in the level. Each will receive one of these items:
any npc_tool (eg. iron hoe)
an apple
any shield
any sword
any book (eg. necronomicon_part2)
Place a tagPoint in the level not too far from the slots. Name it readSpot.
Create an MBT tree for inclusion. Name it reading. Store the implementation in the SA’s xml file.
Definition of reading:
The tree expects t_book (type common:wuid) variable from the parent tree.
Regardless, test for the existence of t_book manually (VariableExistsGate) and copy its value to internal bookItem variable.
Plug the following under a FuseBox node with OneCleanup=true.
Child tree:
Clean NPC’s hands from any items.
Instantly put the book in right hand and make the item invisible
PlayAnimation ReadingBookIn with tag book. In reaction to animation event Attach, make the book item visible.
Play looped animation ReadingBook with tag book and wait for interruption.
Cleanup tree:
Play animation ReadingBookOut, tag book
Put the item in inventory instantly as a reaction to the animation event Detach
Warning: FuseBox OnSuccess and OnFail trees are guaranteed to be executed when a subtree ENDs and take precedence over any other script. By placing anything long-lasting in the cleanup trees you may make the NPC seem stuck and unresponsive.
Safeguard every PlayAnimation node using LODGuardian, LODLock, or LODCheck nodes.
Add a new behavior pickItems to the SA:
Find the items
Go and pick up the npc tool using safePickItem behavior from the slot.
(The npc_tools require the itemSlot to be a SmartObject of type so_slot)
Warning: you must never put items managed by so_slot SmartObjects into inventory or stashes. Pick, place, and drop are the only safe operations.
Drop or place back the npc tool
Go and pick up the apple with right hand and put the item into inventory
Go and pick up the shield with left hand and put the item into inventory
Go and pick up the sword with right hand, equip it, and holster it
Go and pick up the book with right hand and put it in inventory
Establish 2 modes read (default value) and pickStuff
Control script for switching between the modes:
Use OnInventoryEvent.
Whenever the player drops an item mark it with a link and change the mode to pickStuff
Definition of pickStuff mode:
Use Fusebox
Child tree:
Loop
Find a dropped item
Draw a weapon
Pick the item and put it into inventory
End the loop if you don't find any item
Cleanup tree:
Holster the weapon and change the mode to read
Definition of read mode:
Go to readPoint
Include the reading tree
Jump in the game and drop some items when the NPC is in the reading phase (you can use the command wh_pl_MagicBox to give the player some more items)
Newly introduced nodes:
ExistPath
IncludeTree
HadCheck
DoPickUp (also notice the Instant version)
DoPlace (also notice the Instant version)
PutItemInInventory (also notice the Instant version)
PutItemInHand (also notice the Instant version)
IsWeaponDrawn
DrawWeapon (also notice the Instant version)
HolsterWeapon (also notice the Instant version)
OnInventoryEvent
SetVisibility
AnimationEventCatch
FuseBox
SuppressFailure
VariableExistsGate
LODGuardian
LODCheck
LODLock
Extras:
Make the whole script save/load-proof
Implement AI LOD for all the other behavior from previous exercises.
Handle situations, where the player picks the items before the NPC does. With the exception of the book the behavior must be able to cope any of the items missing.