Scripting Tutorial

From Nexus Mods Wiki
Revision as of 23:52, 12 November 2019 by WarhorseStudios (talk | contribs)
Jump to: navigation, search

Script training


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

  1. Basics principles of scripting (via basic NPC scripting)
  2. More complex interactions between NPCs and an NPC and the world
  3. Quest scripting

How to use this tutorial:
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.

 

1.  New brain, new subbrain, first script

Goal: The NPC plays an animation

  1. Create a new level
  2. Insert entity NPC, name it test_ST_npc1
  3. Create a new soul of the same name and give it to the NPC
  4. Create a new brain for this NPC, and name it test_ST_npc_brain
  5. Create a new subbrain in this brain, and name it test_ST_npc1_mainSB
    Save it in a XML file under folder Data/Libs/AI/_test
    Set it AlwaysActive = true.
  6. Add this set of nodes to the newly created subbrain tree
    1. Nodes in Sequence
      • PlayAnimation with parameter
        • animation = GreetingsUpperBody
        • tags= waveSmall
      • Wait with parameters
        • duration = -1

Newly introduced nodes:

  • Root
  • Sequence
  • PlayAnimation
  • Wait

Extras:

  1. Try to do the following:
    1. Insert a brush
    2. Insert a GeomEntity
    3. Paint some vegetation
    4. Paint some terrain texture
    5. Modify the terrain geometry
    6. Apply a texture to primitive GeomEntity (e.g. sphere)
    7. Insert a prefab
 

 

2.  Links, behavior call, movement

Goal: NPC calls a behavior from the SmartArea which makes him move to a tagPoint and play the aimation

  1. Place a tagPoint in the level
  2. Create a link with name destination from the NPC test_ST_npc1 to the tagPoint
  3. Place a SmartArea in the level, name ittest_ST_sa
  4. Create a link with name sa from the NPC to the SmartArea
  5. Create a SmartArea template with nametest_ST_sa
    SmartArea can hold behavior definitions even if the SA has no brain
  6. Create a behavior moveToDestination, held by the SmartArea
    1. Save the XML in file Data/Libs/AI/_test/test_ST_sa.xml
    2. Definition of the behavior:
      1. Find the tagPoint via GraphSearch
      2. Move to the tagPoint
      3. Play the animation from previous exercise
  7. Change the NPCs subbrain tree definition:
    1. Find the SmartArea via GraphSearch
    2. CallBehavior node with parameter BehaviorName= moveToDestination
    3. Wait -1
  8. Generate navmesh  
    1. Place a NavigationArea in the level. Cover the whole level.  
    2. AI -> Request a full NMN rebuild
    3. cvar wh_ai_RecastDebugDraw=1 shows the generated navmesh

Newly introduced nodes:

  • GraphSearch
  • LinkTagFilter
  • Move
  • CallBehavior

Extras:

  1. Add breakpoints to all the nodes and step through the execution, observe the behavior
 

3.  Advanced Move, Dynamic node values, Distance tests, Gates

Goal: NPC will keep moving towards the player

  1. Create a new behavior on the SmartArea, name it moveToPlayer
  2. Definition of moveToPlayer:
    1. The NPC will test how close to the player he is and will set the Move speed to ‘Walk’ or ‘Run’ depending on the distance.
    2. The Move node will:
      • recalculate the path every 200ms (see Move node documentation)
      • stop the NPC 2 meters from the target (player)
    3. Then play the hand-waving animation
    4. Resume the movement if the player gets too far again
  3. Edit the NPC’s subbrain so that it calls the moveToPlayer behavior

Newly introduced nodes:

  • Parallel
  • Expression
  • Loop
  • DistanceGate
  • VarOperation (Reinit)
  • Success

VIDEO COMING SOON

4.  Guard behavior, Loops, Randomization, Save/Load

Goal: 2 behaviors. NPC walks in a loop. NPC keeps walking between random tagPoints. Both behaviors support save/load.

  1. Add several tagPoints in the level and create looped path for the NPC to follow by creating links between the tagPoints
  2. Create a new behavior on the SA, name it patrol:
  3. Definition of patrol:
    1. The NPC will find the initial tagPoint
    2. Move to the initial tagPoint
    3. Loop
      • If there is another tagPoint linked from the previous tagPoint, go to it
      • Wait at each point for several seconds  
  4. Edit the NPC’s subbrain so that it calls the patrol behavior. Test the behavior.
  5. 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.
  6. Create a new behavior on the SA, name it randomPatrol
  7. Definition of randomPatrol:
    1. The NPC finds a random tagPoint via GraphSearch (it must not be the point where the NPC is standing now)
    2. The NPC goes to the random tagPoint, waits there for a few seconds
    3. Loop it
  8. 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
  9. Edit the NPC’s subbrain so that it calls the randomPatrol behavior. Test the behavior.
  10. Add Save/Load Support to the randomPatrol behavior.

Newly introduced nodes:

  • IfCondition
  • Nodalyzer
  • NegationOp
  • WUIDFilter
  • VarOperation (PushBack)

Extras:

  1. 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.
  2. 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.

VIDEO COMING SOON

 


5.  Link data, Custom link types, Dynamic links

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.

  1. 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)
  2. 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)
  3. Create a link from the npc1 to the SA, and name it work_ST[(‘patrol’)]. Note: ‘patrol’ is the data here.
  4. Create a links from the npc2 and npc3 to the SA, and name each work_ST[(‘randomPatrol’)].
  5. Edit the npc subbrain definition so that:
    1. An NPC finds the SA
    2. Stores the link data in a variable
    3. Calls a behavior of the name stored in the variable
  6. 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)
  7. 2 NPCs are now executing the randomPatrol behavior and thus are at risk of going to the same spot and clipping through one another. Utilize the pre-defined autoreverse link-pair reserver/reservee. Change the definition of randomPatrol:
    • When searching for next tagPoint:
      • The NPC excludes tagPoints from which the link reservee exists.  
      • The NPC does a reservation cleanup - deletes any reserver link from the NPC to a tagPoint (if such link exists).
      • Creates a reserver link from the NPC to the tagPoint it selected.

VIDEO COMING SOON

6.  Messages

Goal: The NPC doing the patrol behavior reacts to player entering the SmartArea by inspecting the players original position.

1.    Create a new custom type in ..\Data\Libs\AI\TypeDefinitions.xml

  • name the type test_ST:intruder (that is type and subtype)
  • with one member intruder of the type common:wuid
  • (Note: Adding new types requires editor restart)

2.    Create a new mailbox template definition

  • Name it intruderST
  • Make it accept the new type test_ST:intruder

3.    Add a brain to the SA

  • name the brain test_ST_sa
  • give it all the types of trees and SA-type brain can handle:  OnUpdate/OnEnter/OnLeave/OnRequest/OnRelease
  • save the trees 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 if there is no script yet. Otherwise they will keep failing.

4.    SA’s onEnter tree definition:

  • when player entered the SA, send all NPCs, who are executing the patrol behavior, a message test_ST:intruder with intruder variable carrying the WUID of the intruder (equal to global variable __player in this case).  

5.    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”. “patrolling” will represent the old patrolling logic from previous exercises. Value “inspecting” will represent the new mode, where NPC reacts to an intruder.
  • Create the “inspecting” logic:
    • Move to __player
    • 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 head back to a tagPoint it intended to go to prior to intruder induced interruption.

Extras:

1.    Change the logic so that the NPC goes to the nearest tagPoint when switching back to “patrolling” mode
2.    Make the whole behavior save/load-proof.  

VIDEO UPCOMING SOON

7.  Synchronization

Goal: 2 NPCs keep synchronizing and wave at each other at the same time in random intervals.
1.    Create 2 new NPCs test_ST_npc4 and test_ST_npc5. They should stay face to face about 2 meters apart.
2.    Add new behavior to the SA, name it waveMaster.
1.    Add a loop and in random intervals (5-20 seconds) run a Synchronize node:

  • Lock Name = testST_Wave  
  • LockManagerType = Global
  • Lock Count = 2

2.    When the synchronize node opens it executes waving animation (see exercise I)
3.    Add new behavior to the SA, name it waveSlave.

  • Add a loop and immediately run a Synchronize node. Set it up identically to the one in waveMaster behavior.
  • When the synchronize node opens it also executes the waving animation

4.    Make each NPCs call one of the behaviors.

Extras:

1.    Change the logic so that the NPC goes to the nearest tagPoint when switching back to “patrolling” mode
2.    Try to do the same with node-pair ExternalLock a SetExternalLock
3.    Edit the waveSlave behavior:

  • The NPC keeps turning to player (node TurnBody in a loop) and only when the Synchronization happens the NPC turns back to “master” and waves at him. Then returns back to turning to player.  

4.    Add another NPC with waveSlave behavior. Make sure that only one of these “slave” NPCs react to synchronization by limiting the waving animation via Semaphore node.

VIDEO COMING SOON

8.  AreaPresence

Goal: NPCs inside an area will run a desired behavior when there’s a specified set of entities inside the area.

1.    Place a TriggerArea in the level. Name it triggerArea. Create a link from the SA to thetriggerArea.
2.    Add a newthereAndBackAgain behavior to the SA:

  • Walking between 2 points. One inside and one outside the TriggerArea. Wait at each point for 5-20 seconds.  

3.    Add a new welcomeTraveller behavior to the SA:

  • I turn to the player and wave at him.

4.    Place 4 new NPCs in the level. Place them outside the triggerArea. You will probably need a new NPC brain definition for them.
5.    Make all the NPCs execute thethereAndBackAgain behavior by default
6.    Edit the OnUpdate tree of the SA:

  • Find the TriggerArea
  • Run the AreaPresence node and process its events.  
  • When there’s the player and at least 2 NPCs inside the triggerArea, make the NPCs inside the triggerArea to switch to behavior welcomeTraveller. They will then return to thereAndBackAgain.  
  • The next round of welcomeTraveller execution will only be possible if the player re-enters the area again.  

Extras:  

1.    Make the whole logic save/load-proof

VIDEO COMING SOON

9.  Items, IncludeTree, animation slave objects, behavior cleanups

Goal: NPCs picks up a book, a sword, and a shield, and equips the weapons. Then the NPC reads the book. If the player drops an item on the floor the NPC closes the book, draws a weapon, picks the item, and then returns back to reading.

1.    Place 2 itemSlots in the level. Each will receive one of these items:

  • any book (a document type item with the book model)
  • any sword

2.    Place a tagPoint in the level not too far from the slots. Name it readSpot.
3.    Create an MBT tree for inclusion. Name it readBook. Store the definition in the SA’s xml file. Definition of readBook:

1. Plug everything under a FuseBox node with OneCleanup=true.
2. Phase 1 (intro):

  • Tree expects t_book (type common:wuid) variable from the parent tree.  
  • Cleanup NPC’s hands from any items.
  • Go to the readSpot and align with its orientation
  • Instantly pick the book from inventory and hide the book item (SetVisibility)
  • Play animation readingBookIn, tag book
  • React to animation event Attach, make the book visible when it happens.


3. Phase 2 (reading) is a loop
 

  • Play animation readingBook, tag book

4. Phase 3 is a cleanup tree. Plug it under a FuseBox as On Success subtree.

 

  • 1.    Play animation reading book out, tag book¨
  • 2.    Put the item in inventory as a reaction to the animation event Detach
  • 3.    Warning: FuseBox OnSuccess and/or 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.

4.    Add a new behavior pickItems to the SA:

  • Find the items
  • Go and pick up the book with right hand and put it in inventory
  • Go and pick up the sword in right hand, put it into the inventory and equip it
  • Then in a loop, do these 2 in parallel:
    • include the tree readBook
    • Watch if the player dropped any item.
  • If the player dropped an item interruption occurs. The interruption will work in this way:
    • readBook tree will be terminated from above. readbook must cleanup itself.
    • The NPC will draw a weapon, run to the dropped item, picks it up and puts it in the inventory. Repeats for all dropped objects.
    • If there isn’t any other dropped item, returns back to readBook.

5.    Jump in the game and run the command wh_pl_MagicBox to give the player some items so that you can drop them for the NPC.

Extras:

1.    Remake the picking of the item by calling the correct behavior safePickItem from the slot
2.    Make the whole script save/load-proof

VIDEO COMING SOON

Kingdom Come: Deliverance: Forum | Before you start | Tutorials & Instructions | Basic Mods | Tools | Documentation | Glossary | EULA