Difference between revisions of "Scripting"

From Divinity Engine Wiki
Jump to: navigation, search
Line 10: Line 10:
 
<p>Before any section in a script, we can INCLUDE files. When you INCLUDE a file, you're telling the system to parse that file first.</p>
 
<p>Before any section in a script, we can INCLUDE files. When you INCLUDE a file, you're telling the system to parse that file first.</p>
  
<h2><u>INIT</u></h2>
+
<h3><u>INIT</u></h3>
 
<p>In the INIT section you can declare global variables and inherit from other scripts. Global variables always start with&nbsp;% and can be accessed and modified everywhere (including other scripts, osiris, and in code). There are also EXTERN variables, which are exposed when you assign a script to an object so you can change the value for local instances of the script. The INIT section also contains the special variable '__Me', which contains the owner of the script. This is a character or item depending on the script type. Code will automatically fill this variable.<br>
 
<p>In the INIT section you can declare global variables and inherit from other scripts. Global variables always start with&nbsp;% and can be accessed and modified everywhere (including other scripts, osiris, and in code). There are also EXTERN variables, which are exposed when you assign a script to an object so you can change the value for local instances of the script. The INIT section also contains the special variable '__Me', which contains the owner of the script. This is a character or item depending on the script type. Code will automatically fill this variable.<br>
 
Inheritance is done with the USING keyword and copies everything from the other script into the current script. This includes all global variables, reactions, scriptframes, and events. You can use USING SHARED if you intend to overwrite reactions, events, or scriptframes.<br>
 
Inheritance is done with the USING keyword and copies everything from the other script into the current script. This includes all global variables, reactions, scriptframes, and events. You can use USING SHARED if you intend to overwrite reactions, events, or scriptframes.<br>
Line 35: Line 35:
 
USING ScriptName EVENT EventName
 
USING ScriptName EVENT EventName
 
USING ScriptName SCRIPTFRAME ScriptFrameName
 
USING ScriptName SCRIPTFRAME ScriptFrameName
 +
</pre>
 +
 +
<h3><u>BEHAVIOUR</u></h3>
 +
<p>This section contains the actual behaviour of the object. It solely consists of a whole bunch of reactions, of which only 1 reaction can be active at the same time. The current reaction gets selected based on its priority and its CHECK. The priority defines how much it wants to be executed and the CHECK decided whether its possible to be executed. The reaction with the highest priority whose CHECK succeeds will be selected for execution:</p>
 +
<ul><li><span style="background-color:transparent;font-size:1em">The whole list of reactions is always sorted from HIGH to LOW priorities.</span></li>
 +
<li><span style="background-color:transparent;font-size:1em">We then check in that order all the reactions with a higher priority of the current reaction and see if their CHECK succeeds.</span></li>
 +
<li><span style="background-color:transparent;font-size:1em">As soon as a higher priority reaction's CHECK succeeds, it interrupts the current reaction and will start executing.</span></li></ul>
 +
<p><b>Important note</b>: only the CHECKs of higher priority reactions are evaluated every frame. This means that the current reaction's CHECK is <b>NOT</b> reevaluated while it's executing! It could become invalid during the execution. This is also true when calling Reset(): execution simply restarts at the beginning of the current reaction without evaluating the CHECK conditions again.</p>
 +
<p>On top of the priority and CHECKs there's also the USAGE keyword. A reaction needs to specify a USAGE context. You can pick the following USAGE contexts:
 +
<ul><li>USAGE COMBAT: can only be executed during combat when its the turn of the object</li>
 +
<li>USAGE WAITING: can only be executed during combat when waiting for its turn</li>
 +
<li>USAGE PEACE: can only be executed when not in combat</li>
 +
<li>USAGE ALL: can always be executed</li></ul></p>
 +
<p>A reaction can have its own local variables. These variables have to start with an underscore and can only be accessed within the reaction itself.</p>
 +
<p>As soon as a reaction is interrupted, the INTERRUPT event will be called immediately and you can execute some code to for example Reset the reaction. You can catch all INTERRUPTS or only specific interrupts (like the movement failed interrupt) and execute actions on the interrupt. The actions during an interrupt are limited to immediate/simple actions, which can be executed immediately (e.g. CharacterMoveTo() is not allowed).<br>
 +
Keep in mind though that a reaction can be interrupted for many reasons:</p>
 +
<ul><li>Higher priority reaction takes over.</li>
 +
<li>Other system takes over: statuses/dialog/story</li>
 +
<li>An exception was thrown in the script: e.g. pathfinding failed, ...</li>
 +
<li>The object got culled (more details on culling can be found later on this page)</li></ul>
 +
<p><b>Important note</b>: if a reaction gets interrupted and later gets resumed, it will continue executing where it got interrupted! (unless "Reset" was called in the INTERRUPT handler, in which case it will begin from the start again)</p>
 +
<p>An example BEHAVIOUR section:</p>
 +
<pre>
 +
BEHAVIOUR
 +
REACTION TestReaction, 5 // Priority 5
 +
USAGE PEACE
 +
VARS
 +
    CHARACTER:_TestCharacter
 +
CHECK "(c1|!c2)&!c3" // Reaction can be executed when one of the first 2 conditions passes, and the third condition doesn't pass
 +
    IsInSurface(__Me, SurfaceOil)
 +
    IsInDangerousSurface(__Me)
 +
    CharacterIsFloating(__Me)
 +
ACTIONS
 +
    CharacterFleeFromSurface()
 +
INTERRUPT
 +
ON
 +
    OnMovementFailed(_) // Only when interrupted by a failed movement
 +
ACTIONS
 +
    Reset()
 
</pre>
 
</pre>

Revision as of 14:10, 8 September 2017

Introduction

The scripting system is currently used for the behaviour of characters and items. For characters this is only part of the behaviour, as many more systems are influencing this in the following priority:

  • Story: executing Osiris Tasks
  • Dialogs: dialog behaviour and animations
  • Statuses: dying, frozen, petrified, ...
  • Scripts: executing the current reaction or scriptframe

Scripts are the lowest priority and will only execute if none of the other systems want to do something. For items it's a lot simpler, as the scripting system is the only thing that influences the items behaviour.

Sections

Before any section in a script, we can INCLUDE files. When you INCLUDE a file, you're telling the system to parse that file first.

INIT

In the INIT section you can declare global variables and inherit from other scripts. Global variables always start with % and can be accessed and modified everywhere (including other scripts, osiris, and in code). There are also EXTERN variables, which are exposed when you assign a script to an object so you can change the value for local instances of the script. The INIT section also contains the special variable '__Me', which contains the owner of the script. This is a character or item depending on the script type. Code will automatically fill this variable.
Inheritance is done with the USING keyword and copies everything from the other script into the current script. This includes all global variables, reactions, scriptframes, and events. You can use USING SHARED if you intend to overwrite reactions, events, or scriptframes.
An example INIT section:

INIT
     USING SHARED Base
     CHARACTER:__Me
     CHARACTER:%AnotherCharacter=null
     FLOAT3:%CurrentPosition=null
     EXTERN INT:%CanRun=1

If you want to override a specific reaction, event, or scriptframe you have to specify this with the OVERRIDE keyword:

REACTION TestReaction, 5 OVERRIDE
EVENT TestEvent OVERRIDE
SCRIPTFRAME TestFrame OVERRIDE

If needed, you can also only inherit specific reactions, events, orscriptframes:

USING ScriptName REACTION ReactionName,NewPriority
USING ScriptName EVENT EventName
USING ScriptName SCRIPTFRAME ScriptFrameName

BEHAVIOUR

This section contains the actual behaviour of the object. It solely consists of a whole bunch of reactions, of which only 1 reaction can be active at the same time. The current reaction gets selected based on its priority and its CHECK. The priority defines how much it wants to be executed and the CHECK decided whether its possible to be executed. The reaction with the highest priority whose CHECK succeeds will be selected for execution:

  • The whole list of reactions is always sorted from HIGH to LOW priorities.
  • We then check in that order all the reactions with a higher priority of the current reaction and see if their CHECK succeeds.
  • As soon as a higher priority reaction's CHECK succeeds, it interrupts the current reaction and will start executing.

Important note: only the CHECKs of higher priority reactions are evaluated every frame. This means that the current reaction's CHECK is NOT reevaluated while it's executing! It could become invalid during the execution. This is also true when calling Reset(): execution simply restarts at the beginning of the current reaction without evaluating the CHECK conditions again.

On top of the priority and CHECKs there's also the USAGE keyword. A reaction needs to specify a USAGE context. You can pick the following USAGE contexts:

  • USAGE COMBAT: can only be executed during combat when its the turn of the object
  • USAGE WAITING: can only be executed during combat when waiting for its turn
  • USAGE PEACE: can only be executed when not in combat
  • USAGE ALL: can always be executed

A reaction can have its own local variables. These variables have to start with an underscore and can only be accessed within the reaction itself.

As soon as a reaction is interrupted, the INTERRUPT event will be called immediately and you can execute some code to for example Reset the reaction. You can catch all INTERRUPTS or only specific interrupts (like the movement failed interrupt) and execute actions on the interrupt. The actions during an interrupt are limited to immediate/simple actions, which can be executed immediately (e.g. CharacterMoveTo() is not allowed).
Keep in mind though that a reaction can be interrupted for many reasons:

  • Higher priority reaction takes over.
  • Other system takes over: statuses/dialog/story
  • An exception was thrown in the script: e.g. pathfinding failed, ...
  • The object got culled (more details on culling can be found later on this page)

Important note: if a reaction gets interrupted and later gets resumed, it will continue executing where it got interrupted! (unless "Reset" was called in the INTERRUPT handler, in which case it will begin from the start again)

An example BEHAVIOUR section:

BEHAVIOUR
REACTION TestReaction, 5 // Priority 5
USAGE PEACE
VARS
     CHARACTER:_TestCharacter
CHECK "(c1|!c2)&!c3" // Reaction can be executed when one of the first 2 conditions passes, and the third condition doesn't pass
     IsInSurface(__Me, SurfaceOil)
     IsInDangerousSurface(__Me)
     CharacterIsFloating(__Me)
ACTIONS
     CharacterFleeFromSurface()
INTERRUPT
ON
     OnMovementFailed(_) // Only when interrupted by a failed movement
ACTIONS
     Reset()