Difference between revisions of "Osiris Overview"
(Osiris overview (WIP)) |
(Documented rule actions, user queries and procedures) |
||
Line 29: | Line 29: | ||
IF | IF | ||
DB_MyPrefix_Fruit("Pear") // This rule will fire when DB_MyPrefix_Fruit("Pear") gets defined. | DB_MyPrefix_Fruit("Pear") // This rule will fire when DB_MyPrefix_Fruit("Pear") gets defined. | ||
− | AND // Osiris rule conditions can only be combined with AND, not with OR. There | + | AND // Osiris rule conditions can only be combined with AND, not with OR. There is |
− | // | + | // another ways to implement OR-conditions though, explained below (user-defined queries). |
NOT DB_MyPrefix_Fruit("Lemon") // This rule's actions will only execute if no DB_MyPrefix_Fruit("Lemon") entry exists. | NOT DB_MyPrefix_Fruit("Lemon") // This rule's actions will only execute if no DB_MyPrefix_Fruit("Lemon") entry exists. | ||
THEN | THEN | ||
Line 43: | Line 43: | ||
NOT DB_MyPrefix_AtLeastOneFruit(1); // in an error. Removing a non-existent database is simply ignored. | NOT DB_MyPrefix_AtLeastOneFruit(1); // in an error. Removing a non-existent database is simply ignored. | ||
</pre> | </pre> | ||
+ | |||
+ | Note that you do not type '''INIT''', '''KB''' and '''FINI''' in the [[Story_editor|story editor]]. These sections are represented by the separate panes that you see when opening a goal for editing. | ||
= Program Structure = | = Program Structure = | ||
Line 132: | Line 134: | ||
.. | .. | ||
] | ] | ||
+ | |||
+ | You can have multiple rules that check on the same trigger condition. They will all be executed when the trigger condition is satisfied. | ||
=== Trigger Conditions === | === Trigger Conditions === | ||
Line 142: | Line 146: | ||
<pre> | <pre> | ||
IF | IF | ||
− | DB_IsPlayer(CHARACTERGUID_S_Player_Ifan_ad9a3327-4456-42a7-9bf4-7ad60cc9e54f) | + | DB_IsPlayer(CHARACTERGUID_S_Player_Ifan_ad9a3327-4456-42a7-9bf4-7ad60cc9e54f) // Trigger condition |
AND | AND | ||
− | DB_Dead(CHARACTERGUID_S_Player_Ifan_ad9a3327-4456-42a7-9bf4-7ad60cc9e54f) | + | DB_Dead(CHARACTERGUID_S_Player_Ifan_ad9a3327-4456-42a7-9bf4-7ad60cc9e54f) // Trigger condition |
THEN | THEN | ||
DB_IfanIsDeadAsAPlayer(1); | DB_IfanIsDeadAsAPlayer(1); | ||
Line 153: | Line 157: | ||
=== Extra Conditions === | === Extra Conditions === | ||
Extra conditions are any conditions that are checked as part of a rule, but that are not trigger conditions: | Extra conditions are any conditions that are checked as part of a rule, but that are not trigger conditions: | ||
− | + | ; Osiris Query : Osiris can [[:Category:Osiris_Queries|query]] the game engine about many aspects of the current game state, or the state of an object. | |
− | : Comparison: You can use the comparison operators '''==''' (equals), '''!=''' (different), '''<=''', '''<''', '''>''' and '''>=''' as a condition. [[# | + | ; User Query : You can also define [[#Queries|your own queries]] in Osiris. This way you can implement OR-conditions. |
− | + | ; Comparison: You can use the comparison operators '''==''' (equals), '''!=''' (different), '''<=''', '''<''', '''>''' and '''>=''' as a condition. [[#Types|GUIDSTRING]] and its descendent types can only be compared for (non-)equality, while the other types can also be used with the other comparison operators. | |
+ | ; Database : Any database checks after an event trigger condition, or after a query, becomes part of the extra conditions. This means that defining such a database will not trigger this rule's evaluation. Only when this rule is triggered via one of its trigger conditions, this database will be consulted. On the other hand, this means that you can also use negative database fact checks as an extra condition. | ||
− | + | Examples: | |
<pre> | <pre> | ||
IF | IF | ||
− | CharacterDied(CHARACTERGUID_S_Player_Ifan_ad9a3327-4456-42a7-9bf4-7ad60cc9e54f) | + | CharacterDied(CHARACTERGUID_S_Player_Ifan_ad9a3327-4456-42a7-9bf4-7ad60cc9e54f) // Trigger condition |
AND | AND | ||
− | DB_IsPlayer(CHARACTERGUID_S_Player_Ifan_ad9a3327-4456-42a7-9bf4-7ad60cc9e54f) | + | NOT DB_IsPlayer(CHARACTERGUID_S_Player_Ifan_ad9a3327-4456-42a7-9bf4-7ad60cc9e54f) // Extra condition |
THEN | THEN | ||
DB_IfanDiedAsAPlayer(1); | DB_IfanDiedAsAPlayer(1); | ||
</pre> | </pre> | ||
− | In this case, ''DB_IfanDiedAsAPlayer(1)'' will only be defined if the ''CharacterDied'' event arrives while he was | + | In this case, ''DB_IfanDiedAsAPlayer(1)'' will only be defined if the ''CharacterDied'' event arrives while he was not in the ''DB_IsPlayer'' database. If you remove him from that database after he died, no new ''CharacterDied'' event will be sent and hence the rule will not re-evaluated. |
+ | |||
+ | <pre> | ||
+ | IF | ||
+ | DB_IsPlayer(CHARACTERGUID_S_Player_Ifan_ad9a3327-4456-42a7-9bf4-7ad60cc9e54f) // Trigger condition | ||
+ | AND | ||
+ | CharacterIsDead(CHARACTERGUID_S_Player_Ifan_ad9a3327-4456-42a7-9bf4-7ad60cc9e54f, 0) // Extra condition | ||
+ | THEN | ||
+ | DB_IfanIsAPlayerAndNotDead(1); | ||
+ | </pre> | ||
+ | |||
+ | ''CharacterIsDead'' is a query that takes two parameters: a CHARACTERGUID and an integer. The integer indicates whether the character is dead (1) or not (0). If all parameters match, the query condition will succeed. This means that the above rule succeeds in case ''CHARACTERGUID_S_Player_Ifan_ad9a3327-4456-42a7-9bf4-7ad60cc9e54f'' is not dead at the time he gets added to the ''DB_IsPlayer'' database. | ||
=== Actions === | === Actions === | ||
+ | All statements in the '''INIT''' and '''FINI''' sections must be actions. Additionally, at the end of [[#Rule|rule]] you must also put at least one action. Every action ends in a semi-colon (''';'''). | ||
+ | |||
+ | Actions can make changes to both the Osiris program state and the game state: | ||
+ | ; Osiris Call : Osiris can [[:Category:Osiris_Queries|call]] into the game engine to perform all kinds of changes to the current game state, either globally or specific to certain objects. | ||
+ | ; Procedure : You can group conditions and actions in a [[#Procedures|procedure]] and call that from multiple locations. | ||
+ | ; Database : As shown in the examples above, a new database fact can be added in an action by placing it there, followed by a semi-colon. You can also remove database facts by prepending it with '''NOT'''. | ||
+ | |||
+ | Example: | ||
+ | <pre> | ||
+ | IF | ||
+ | DB_IsPlayer(CHARACTERGUID_S_Player_Ifan_ad9a3327-4456-42a7-9bf4-7ad60cc9e54f) // Trigger condition | ||
+ | AND | ||
+ | CharacterIsDead(CHARACTERGUID_S_Player_Ifan_ad9a3327-4456-42a7-9bf4-7ad60cc9e54f, 1) // Extra condition | ||
+ | THEN | ||
+ | CharacterResurrect(CHARACTERGUID_S_Player_Ifan_ad9a3327-4456-42a7-9bf4-7ad60cc9e54f); | ||
+ | </pre> | ||
+ | |||
+ | If Ifan is added to the ''DB_IsPlayer'' database while he was already dead, he will be resurrected. | ||
== Subroutines == | == Subroutines == | ||
+ | |||
+ | There are two kinds of user-defined subroutines in Osiris: queries and procedures. | ||
+ | |||
+ | === Queries === | ||
+ | Queries are new compared to DOS1. They are very handy to implement OR-conditions. The format of a query is: | ||
+ | '''QRY''' | ||
+ | ''QRY_MyPrefix_QueryName'''''(('''''TYPE1''''')'''''_Param1''[''',('''''TYPE2''''')'''''_Param2..'']''')''' | ||
+ | [ | ||
+ | '''AND''' | ||
+ | ''ExtraCondition1'' | ||
+ | .. | ||
+ | ] | ||
+ | '''THEN''' | ||
+ | ''Action1''''';''' | ||
+ | [ | ||
+ | ''Action2''''';''' | ||
+ | .. | ||
+ | ] | ||
+ | |||
+ | Just like databases, queries can be overloaded with different numbers of parameters. And just like with [[#Rules|rules]], you can define the same query multiple times and every definition of that same query will be executed when it is called. When a user-defined query is called in a rule, it is considered to have succeeded if the conditions of at least one definition of this query all succeeded (and hence its actions were executed). | ||
+ | |||
+ | Example: | ||
+ | <pre> | ||
+ | QRY | ||
+ | QRY_Overview_CharacterIsIncapacitated((CHARACTERGUID)_Char) | ||
+ | AND | ||
+ | HasActiveStatus(_Char,"FROZEN",1) | ||
+ | THEN | ||
+ | DB_NOOP(1); | ||
+ | |||
+ | QRY | ||
+ | QRY_Overview_CharacterIsIncapacitated(_Char) | ||
+ | AND | ||
+ | HasActiveStatus(_Char,"PETRIFIED",1) | ||
+ | THEN | ||
+ | DB_NOOP(1); | ||
+ | |||
+ | QRY | ||
+ | QRY_Overview_CharacterIsIncapacitated(_Char) | ||
+ | AND | ||
+ | HasActiveStatus(_Char,"KNOCKED_DOWN",1) | ||
+ | THEN | ||
+ | DB_NOOP(1); | ||
+ | |||
+ | IF | ||
+ | CharacterReceivedDamage(_Char) | ||
+ | AND | ||
+ | // check whether _Char is FROZEN, PETRIFIED or KNOCKED_DOWN | ||
+ | QRY_Overview_CharacterIsIncapacitated(_Char) | ||
+ | THEN | ||
+ | DB_Overview_CowardAttackingIncapacitatedCharacter(1); | ||
+ | </pre> | ||
+ | |||
+ | Our ''QRY_Overview_CharacterIsIncapacitated'' query does not actually need to do anything if its conditions succeed. However, you cannot have condition checks without an action. For this reason, we define a dummy database fact (''DB_NOOP(1)'' by convention) if all rules succeed. So, our rule calls ''QRY_Overview_CharacterIsIncapacitated(_Char)'' then Osiris will evaluate all definitions of this query in the entire story (so not just in the current [[#Program_Structure|goal]!]) and if one of them succeeds, it will consider this query/extra condition to have succeeded.. | ||
+ | |||
+ | Just like with databases, only the first encountered definition of a query needs to specify the parameter type(s). It does not hurt to repeat them though. | ||
+ | |||
+ | Note that Osiris has a [[Osiris/API/CharacterIsIncapacitated|built-in query]] to check whether a character is incapacitated (which checks more statuses than the ones listed above), so you won't need to implement the above query yourself. | ||
+ | |||
=== Procedures === | === Procedures === | ||
− | + | Procedures are collections of actions that you may want to execute from multiple action blocks. Or, they may be a number of related actions of which some must only be executed under certain conditions. In this sense, they allow you to have extra condition checks in an action block. | |
+ | |||
+ | The format of a procedure is: | ||
+ | '''PROC''' | ||
+ | ''PROC_MyPrefix_ProcName'''''(('''''TYPE1''''')'''''_Param1''[''',('''''TYPE2''''')'''''_Param2..'']''')''' | ||
+ | [ | ||
+ | '''AND''' | ||
+ | ''ExtraCondition1'' | ||
+ | .. | ||
+ | ] | ||
+ | '''THEN''' | ||
+ | ''Action1''''';''' | ||
+ | [ | ||
+ | ''Action2''''';''' | ||
+ | .. | ||
+ | ] | ||
+ | |||
+ | Just like with queries, you can have multiple definitions of the same procedure (even in different [[#Program_Structure|goals]). When you invoke a procedure, all of those procedures will be executed. Unlike queries, procedures have no result, since statements in an action block do not return anything. | ||
+ | |||
+ | Example: | ||
+ | <pre> | ||
+ | PROC | ||
+ | PROC_Overview_TeleportAlive((CHARATERGUID)_Char, (GUIDSTRING)_Destination) | ||
+ | AND | ||
+ | CharacterIsDead(_Char, 1) | ||
+ | THEN | ||
+ | CharacterResurrect(_Char); | ||
+ | |||
+ | PROC | ||
+ | PROC_Overview_TeleportAlive(_Char, _Destination) | ||
+ | THEN | ||
+ | TeleportTo(_Char, _Destination); | ||
+ | </pre> | ||
+ | |||
+ | This procedure will first check whether the character to be teleported is dead. If so, the character will be resurrected. Next, the character (which is guaranteed to be alive now) is teleported to its destination, which can be a trigger, item, or other character. | ||
+ | |||
+ | This way you can check extra conditions in an actions block, by delegating the checks to a procedure. | ||
= Program Execution = | = Program Execution = | ||
+ | |||
+ | |||
== Goal Initialisation and Completion == | == Goal Initialisation and Completion == | ||
+ | == Rule Matching == | ||
== Rule Precedence == | == Rule Precedence == |
Revision as of 20:09, 16 September 2017
Contents
Introduction
Osiris is a mostly declarative programming language, similar to Prolog in some ways. While it is somewhat counter-intuitive at first if you are not used to declarative programming, the language itself is quite simple and only has a few constructs. Its power mainly stems from its access to a very large number of APIs (note: work-in-progress; there are 879 Osiris APIs at the time of writing). These APIs allow you to react to things that happen (events), to obtain information about the current game state (queries), and to change the game state (calls).
If you are familiar with databases, you can think of an Osiris program as a collection of rules that operate on a single, game-wide database. The rules dynamically add and remove tables and rows in reaction to other database entries getting modified. Additionally, they can also react to game state changes, query the game state, and change the game state. Note that the tables are actually referred to as "databases" in Osiris, so below we will talk about defining databases rather than tables. Adding a row to a table is generally referred to as "defining a fact", although "defining a database" or "defining a database entry" is also used from time to time.
Here is an example Osiris code fragment:
INIT DB_MyPrefix_Fruit("Apple"); // Defines the database DB_MyPrefix_Fruit, and adds a fact consisting of the string "Apple" to it. DB_MyPrefix_Fruit("Pear"); // Adds another fact to the DB_MyPrefix_Fruit database. DB_MyPrefix_Fruit("Banana"); // And one more. KB IF // Osiris rules always start with "IF". DB_MyPrefix_Fruit(_SomeFruit) // This rule will fire when a fact to one-column "DB_MyPrefix_Fruit" database. // gets added. The name of the fruit will be bound to the _SomeFruit variable. THEN // "THEN" indicates that the rule conditions have finished, and the rule actions start. DB_MyPrefix_AtLeastOneFruitDefined(1); // We define a new database with the name "DB_MyPrefix_AtLeastOneFruitDefined", IF // Osiris rules always start with IF DB_MyPrefix_Fruit(_SomeFruit) // This rule will fire when any database with the name "DB_MyPrefix_Fruit" gets defined. // The name of the fruit will be stored in (bound to) the _SomeFruit variable. THEN // "THEN" indicates that the rule conditions have finished, and the rule actions follow. DB_MyPrefix_AtLeastOneFruit(1); // We define a new database with the name "DB_MyPrefix_AtLeastOneFruitDefined", // and add a fact with the integer value 1 to it. Since there are three rows in the // DB_MyPrefix_Fruit database, this action will execute three times. However, as the value // is always the same (1), in the end the DB_MyPrefix_AtLeastOneFruitDefined database will // contain only a single fact, which consists of the value 1. IF DB_MyPrefix_Fruit("Pear") // This rule will fire when DB_MyPrefix_Fruit("Pear") gets defined. AND // Osiris rule conditions can only be combined with AND, not with OR. There is // another ways to implement OR-conditions though, explained below (user-defined queries). NOT DB_MyPrefix_Fruit("Lemon") // This rule's actions will only execute if no DB_MyPrefix_Fruit("Lemon") entry exists. THEN DB_MyPrefix_PearNoLemon(1); EXIT NOT DB_MyPrefix_Fruit("Apple"); // Remove the "Apple" fact from the DB_MyPrefix_Fruit when the goal completes. NOT DB_MyPrefix_Fruit("Pear"); // Removing databases that are no longer used anywhere else after a goal completes NOT DB_MyPrefix_Fruit("Banana"); // reduces savegame sizes and speeds up the game. NOT DB_MyPrefix_AtLeastOneFruit(1); // Even if some of these databases would not exist, removing them would not result NOT DB_MyPrefix_AtLeastOneFruit(1); // in an error. Removing a non-existent database is simply ignored.
Note that you do not type INIT, KB and FINI in the story editor. These sections are represented by the separate panes that you see when opening a goal for editing.
Program Structure
An Osiris program is called a Story. A story consists of all (story) Goals' for the current mod and its dependencies. E.g., the main game mod (DivinityOrigins) depends on the Shared mod, so the story of the main game includes all goals from both mods.
A goal is a plain text file with three sections:
- INIT
- The INIT section contains actions that are executed when the goal initialises.
- KB
- The KB section, or knowledge base, contains rules that become active as soon as the goal starts initialising. This means that the rules in the KB section can react to changes made in the INIT section of the same goal. The KB section cannot contain actions outside rule bodies.
- EXIT
- The EXIT section contains actions that are executed when the goal completes.
Types
Osiris supports ten different types:
- INTEGER
- A 32-bit integer number. Examples: -4, 0, 10.
- INTEGER64
- A 64-bit integer number. Examples: -99999999999, -4, 0, 10, 12345678901.
- REAL
- A single precision floating point number. Examples: -10.0, -0.1, 0.0, 0.5, 100.123.
- STRING
- A character string. Examples: "A", "ABC", "_This is a string_".
- GUIDSTRING
- A GUID that refers to an object, or sometimes root template, in the game. This object can be any of the specialised GUID-types below. Examples: 123e4567-e89b-12d3-a456-426655440000, MyObjectName_123e4567-e89b-12d3-a456-426655440000, CHARACTERGUID_MyObjectName_123e4567-e89b-12d3-a456-426655440000. Osiris in fact only uses the GUID itself. Anything before it (as long as it does not contain "-") gets ignored by the compiler, and is only there to improve the readability of the code. This ensures that if you rename an object in game, its related scripting won't break.
- CHARACTERGUID
- A GUIDSTRING referring to referring a character object in the game. Examples: (CHARACTERGUID)123e4567-e89b-12d3-a456-426655440000, (CHARACTERGUID)MyObjectName_123e4567-e89b-12d3-a456-426655440000, (CHARACTERGUID)CHARACTERGUID_MyObjectName_123e4567-e89b-12d3-a456-426655440000
- ITEMGUID
- A GUIDSTRING referring to referring an item object in the game.
- TRIGGERGUID
- A GUIDSTRING referring to referring a trigger object in the game.
- SPLINEGUID
- A GUIDSTRING referring to referring a spline object in the game.
- LEVELTEMPLATEGUID
- A GUIDSTRING referring to referring a level template object in the game.
The specialised GUID-types all inherit from GUIDSTRING, and you can cast a GUIDSTRING to any of the specific types by adding (ITEMGUID) and the like in front of it. Note that while code-completion will prepend the object type to the object name in the form of ITEMGUID_, this does _not_ define or change the type in any way to the Osiris compiler.
Databases
Osiris databases consist of a database name, which must start with DB_, and one or more typed columns. In practice, since there is only one namespace, it is a good idea to follow the DB_ by a prefix that is unique to your mod or goal. Every entry that gets added to a database is called a fact. The structure of a fact definition is
DB_Prefix_DatabaseName(TypedValue1[,TypedValue2..]);
Examples:
// Type: String DB_Overview_StringDB("SomeString"); DB_Overview_StringDB("AnotherString"); // Type: float DB_Overview_FloatDB(1.0); // Type: Integer, String DB_Overview_IntegerStringDB(0, "String0"); DB_Overview_IntegerStringDB(1, "String1"); DB_Overview_IntegerStringDB(1, "String1"); // Type: GUIDSTRING (not CHARACTERGUID, because no typecast) DB_Overview_Origins(CHARACTERGUID_S_Player_Ifan_ad9a3327-4456-42a7-9bf4-7ad60cc9e54f); DB_Overview_Origins(CHARACTERGUID_S_Player_Beast_f25ca124-a4d2-427b-af62-df66df41a978); DB_Overview_Origins(CHARACTERGUID_S_Player_Lohse_bb932b13-8ebf-4ab4-aac0-83e6924e4295); DB_Overview_Origins(CHARACTERGUID_S_Player_RedPrince_a26a1efb-cdc8-4cf3-a7b2-b2f9544add6f); DB_Overview_Origins(CHARACTERGUID_S_Player_Sebille_c8d55eaf-e4eb-466a-8f0d-6a9447b5b24c); DB_Overview_Origins(CHARACTERGUID_S_Player_Fane_02a77f1f-872b-49ca-91ab-32098c443beb); // Type: CHARACTERGUID, String DB_Overview_Origins((CHARACTERGUID)CHARACTERGUID_S_Player_Ifan_ad9a3327-4456-42a7-9bf4-7ad60cc9e54f, "IFAN"); DB_Overview_Origins(CHARACTERGUID_S_Player_Beast_f25ca124-a4d2-427b-af62-df66df41a978, "BEAST"); DB_Overview_Origins(CHARACTERGUID_S_Player_Lohse_bb932b13-8ebf-4ab4-aac0-83e6924e4295, "LOHSE"); DB_Overview_Origins(CHARACTERGUID_S_Player_RedPrince_a26a1efb-cdc8-4cf3-a7b2-b2f9544add6f, "RED PRINCE"); DB_Overview_Origins(CHARACTERGUID_S_Player_Sebille_c8d55eaf-e4eb-466a-8f0d-6a9447b5b24c, "SEBILLE"); DB_Overview_Origins(CHARACTERGUID_S_Player_Fane_02a77f1f-872b-49ca-91ab-32098c443beb, "FANE");
As the last example shows it is possible to overload databases: you can have multiple databases with the same name. The only requirement is that they have a different number columns (parameters). Also note that this last example only includes a (CHARACTERGUID) typecast for the first row. That is sufficient because the compiler uses the first occurrence of a database in story to determine the types. It will then typecast the values in any further occurrences of this database to the type it determined from the first declaration. Since a GUIDSTRING can be typecasted into a CHARACTERGUID, this works fine here. This is an important rule to keep in mind, as it is a frequent source of type errors when you're just starting out.
Removing database facts can be done using the NOT-operator:
NOT DB_Overview_StringDB("SomeString"); NOT DB_Overview_StringDB("AnotherString");
Rules
A Rule consist of at least one trigger condition, a number of optional extra conditions, and finally one or more actions. A rule is evaluated whenever one of its trigger conditions get becomes fulfilled. If all trigger and extra conditions are fulfilled at that point, the actions of this rule are executed.
The structure of a rule is as follows:
IF TriggerCondition1 [ AND TriggerCondition2 .. ] [ AND ExtraCondition1 .. ] THEN Action1; [ Action2; .. ]
You can have multiple rules that check on the same trigger condition. They will all be executed when the trigger condition is satisfied.
Trigger Conditions
There are two categories of trigger conditions:
- Osiris Event
- The game informs Osiris that a particular event has occurred. Warning: an event can only be used as the first trigger condition. Putting it at any other place in a rule condition should result in a compilation error (and it usually will, but there are some known bugs in the compiler where it will not print an error and generate non-working code instead).
- Database
- A new database fact has been added. Conversely, removing a database fact never results in any kind of event that can be caught as a trigger event.
Note that you can check multiple databases as part of your trigger conditions, and the rule's trigger conditions will be considered as fulfilled as soon as all database checks succeed. Example:
IF DB_IsPlayer(CHARACTERGUID_S_Player_Ifan_ad9a3327-4456-42a7-9bf4-7ad60cc9e54f) // Trigger condition AND DB_Dead(CHARACTERGUID_S_Player_Ifan_ad9a3327-4456-42a7-9bf4-7ad60cc9e54f) // Trigger condition THEN DB_IfanIsDeadAsAPlayer(1);
This rule will trigger if DB_IsPlayer() is defined for Ifan first and then his DB_Dead(), but also if they are defined in the opposite order.
Extra Conditions
Extra conditions are any conditions that are checked as part of a rule, but that are not trigger conditions:
- Osiris Query
- Osiris can query the game engine about many aspects of the current game state, or the state of an object.
- User Query
- You can also define your own queries in Osiris. This way you can implement OR-conditions.
- Comparison
- You can use the comparison operators == (equals), != (different), <=, <, > and >= as a condition. GUIDSTRING and its descendent types can only be compared for (non-)equality, while the other types can also be used with the other comparison operators.
- Database
- Any database checks after an event trigger condition, or after a query, becomes part of the extra conditions. This means that defining such a database will not trigger this rule's evaluation. Only when this rule is triggered via one of its trigger conditions, this database will be consulted. On the other hand, this means that you can also use negative database fact checks as an extra condition.
Examples:
IF CharacterDied(CHARACTERGUID_S_Player_Ifan_ad9a3327-4456-42a7-9bf4-7ad60cc9e54f) // Trigger condition AND NOT DB_IsPlayer(CHARACTERGUID_S_Player_Ifan_ad9a3327-4456-42a7-9bf4-7ad60cc9e54f) // Extra condition THEN DB_IfanDiedAsAPlayer(1);
In this case, DB_IfanDiedAsAPlayer(1) will only be defined if the CharacterDied event arrives while he was not in the DB_IsPlayer database. If you remove him from that database after he died, no new CharacterDied event will be sent and hence the rule will not re-evaluated.
IF DB_IsPlayer(CHARACTERGUID_S_Player_Ifan_ad9a3327-4456-42a7-9bf4-7ad60cc9e54f) // Trigger condition AND CharacterIsDead(CHARACTERGUID_S_Player_Ifan_ad9a3327-4456-42a7-9bf4-7ad60cc9e54f, 0) // Extra condition THEN DB_IfanIsAPlayerAndNotDead(1);
CharacterIsDead is a query that takes two parameters: a CHARACTERGUID and an integer. The integer indicates whether the character is dead (1) or not (0). If all parameters match, the query condition will succeed. This means that the above rule succeeds in case CHARACTERGUID_S_Player_Ifan_ad9a3327-4456-42a7-9bf4-7ad60cc9e54f is not dead at the time he gets added to the DB_IsPlayer database.
Actions
All statements in the INIT and FINI sections must be actions. Additionally, at the end of rule you must also put at least one action. Every action ends in a semi-colon (;).
Actions can make changes to both the Osiris program state and the game state:
- Osiris Call
- Osiris can call into the game engine to perform all kinds of changes to the current game state, either globally or specific to certain objects.
- Procedure
- You can group conditions and actions in a procedure and call that from multiple locations.
- Database
- As shown in the examples above, a new database fact can be added in an action by placing it there, followed by a semi-colon. You can also remove database facts by prepending it with NOT.
Example:
IF DB_IsPlayer(CHARACTERGUID_S_Player_Ifan_ad9a3327-4456-42a7-9bf4-7ad60cc9e54f) // Trigger condition AND CharacterIsDead(CHARACTERGUID_S_Player_Ifan_ad9a3327-4456-42a7-9bf4-7ad60cc9e54f, 1) // Extra condition THEN CharacterResurrect(CHARACTERGUID_S_Player_Ifan_ad9a3327-4456-42a7-9bf4-7ad60cc9e54f);
If Ifan is added to the DB_IsPlayer database while he was already dead, he will be resurrected.
Subroutines
There are two kinds of user-defined subroutines in Osiris: queries and procedures.
Queries
Queries are new compared to DOS1. They are very handy to implement OR-conditions. The format of a query is:
QRY QRY_MyPrefix_QueryName((TYPE1)_Param1[,(TYPE2)_Param2..]) [ AND ExtraCondition1 .. ] THEN Action1; [ Action2; .. ]
Just like databases, queries can be overloaded with different numbers of parameters. And just like with rules, you can define the same query multiple times and every definition of that same query will be executed when it is called. When a user-defined query is called in a rule, it is considered to have succeeded if the conditions of at least one definition of this query all succeeded (and hence its actions were executed).
Example:
QRY QRY_Overview_CharacterIsIncapacitated((CHARACTERGUID)_Char) AND HasActiveStatus(_Char,"FROZEN",1) THEN DB_NOOP(1); QRY QRY_Overview_CharacterIsIncapacitated(_Char) AND HasActiveStatus(_Char,"PETRIFIED",1) THEN DB_NOOP(1); QRY QRY_Overview_CharacterIsIncapacitated(_Char) AND HasActiveStatus(_Char,"KNOCKED_DOWN",1) THEN DB_NOOP(1); IF CharacterReceivedDamage(_Char) AND // check whether _Char is FROZEN, PETRIFIED or KNOCKED_DOWN QRY_Overview_CharacterIsIncapacitated(_Char) THEN DB_Overview_CowardAttackingIncapacitatedCharacter(1);
Our QRY_Overview_CharacterIsIncapacitated query does not actually need to do anything if its conditions succeed. However, you cannot have condition checks without an action. For this reason, we define a dummy database fact (DB_NOOP(1) by convention) if all rules succeed. So, our rule calls QRY_Overview_CharacterIsIncapacitated(_Char) then Osiris will evaluate all definitions of this query in the entire story (so not just in the current [[#Program_Structure|goal]!]) and if one of them succeeds, it will consider this query/extra condition to have succeeded..
Just like with databases, only the first encountered definition of a query needs to specify the parameter type(s). It does not hurt to repeat them though.
Note that Osiris has a built-in query to check whether a character is incapacitated (which checks more statuses than the ones listed above), so you won't need to implement the above query yourself.
Procedures
Procedures are collections of actions that you may want to execute from multiple action blocks. Or, they may be a number of related actions of which some must only be executed under certain conditions. In this sense, they allow you to have extra condition checks in an action block.
The format of a procedure is:
PROC PROC_MyPrefix_ProcName((TYPE1)_Param1[,(TYPE2)_Param2..]) [ AND ExtraCondition1 .. ] THEN Action1; [ Action2; .. ]
Just like with queries, you can have multiple definitions of the same procedure (even in different [[#Program_Structure|goals]). When you invoke a procedure, all of those procedures will be executed. Unlike queries, procedures have no result, since statements in an action block do not return anything.
Example:
PROC PROC_Overview_TeleportAlive((CHARATERGUID)_Char, (GUIDSTRING)_Destination) AND CharacterIsDead(_Char, 1) THEN CharacterResurrect(_Char); PROC PROC_Overview_TeleportAlive(_Char, _Destination) THEN TeleportTo(_Char, _Destination);
This procedure will first check whether the character to be teleported is dead. If so, the character will be resurrected. Next, the character (which is guaranteed to be alive now) is teleported to its destination, which can be a trigger, item, or other character.
This way you can check extra conditions in an actions block, by delegating the checks to a procedure.