Scripting Tutorial

From Nexus Mods Wiki
Revision as of 22:52, 13 February 2020 by Ali3kaa (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
 
 

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.
 
 

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

Newly introduced nodes:

  • AddLink
  • RemoveLink
  • Semaphore
 
 

6. Messages

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

  1. 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
  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 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.
  4. 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.
  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”. Mode “patrolling” will represent the old patrol logic from previous exercises. Value “inspecting” will represent the new mode, where NPC reacts to an intruder.
  6. Create the “inspecting” logic:
    1. Move to the intruder, run if the intruder is far
    2. After the movement, test if the intruder is still close
      • If the intruder is close:
      1. Play animation ADLG_Emphasis with tag "angry"
      2. Then keep turning to the player for a period of time using TurnBody.
      • If the intruder is far:
      1. Play animation LookingAround (no tags) for several seconds
    3. Then set mode back to “patrolling”
  7. 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:

  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.  
 
 

7. Synchronization, External Locks, AreaPresence

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.

  1. Create a regular square-shaped triggerArea
  2. 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.
  3. 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
  4. Add a new behavior to the SA, name it waveMaster. Defintion of the behavior:
    1. Find the two points - wavePoint and hidePoint
    2. Feature a variable mode with 2 possible values "wave" and "hide" and create a control structure which reacts to changes in mode value (ContinuousSwitch)
    3. hide mode definition:
      • Go to hidePoint and wait
    4. 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
    5. 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.
  5. 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
  6. 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.
  7. 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
  8. Add a new behavior to the SA, name it stalker. The NPC will be crossing the triggArea in random intervals. Definition of the behavior:
    1. Find the 2 stalker points
    2. Go between them in a loop
    3. At each stop, play the animation LookingAround for random amount of time
  9. Expand the unused onUpdate tree of SmartArea and turn into an area-intruder observing system:
    1. Find the triggerArea
    2. Store player and stalker NPC in an array
    3. 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:

  1. Make the whole script save/load proof
  2. 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.
  3. 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.
  4. 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.
 
 


8. Items, AI LODing, cleanups

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.

  1. 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)
  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 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:
        1. Clean NPC’s hands from any items.
        2. Instantly put the book in right hand and make the item invisible
        3. PlayAnimation ReadingBookIn with tag book. In reaction to animation event Attach, make the book item visible.
        4. Play looped animation ReadingBook with tag book and wait for interruption.
      • Cleanup tree:
        1. Play animation ReadingBookOut, tag book
        2. Put the item in inventory instantly as a reaction to the animation event Detach
        3. 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.
  4. Add a new behavior pickItems to the SA:
    1. Find the items
    2. 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
    3. Go and pick up the apple with right hand and put the item into inventory
    4. Go and pick up the shield with left hand and put the item into inventory
    5. Go and pick up the sword with right hand, equip it, and holster it
    6. Go and pick up the book with right hand and put it in inventory
    7. Establish 2 modes read (default value) and pickStuff
    8. Control script for switching between the modes:
      1. Use OnInventoryEvent.
      2. Whenever the player drops an item mark it with a link and change the mode to pickStuff
    9. Definition of pickStuff mode:
      • Use Fusebox
      • Child tree:
        1. Loop
        2. Find a dropped item
        3. Draw a weapon
        4. Pick the item and put it into inventory
        5. End the loop if you don't find any item
      • Cleanup tree:
        1. Holster the weapon and change the mode to read
    10. Definition of read mode:
      1. Go to readPoint
      2. Include the reading tree
  5. 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:

  1. Make the whole script save/load-proof
  2. Implement AI LOD for all the other behavior from previous exercises.
  3. 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.

<iframe width="1205" height="753" src="https://www.youtube.com/embed/5obdAAstplI" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

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