Osiris Design Patterns

From Divinity Engine Wiki
Revision as of 09:16, 20 September 2017 by Tinkerer (talk | contribs) (Osiris Design Patterns - Program initialisation WIP)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Introduction

Here we give an overview of a number of common design patterns that are useful when programming in Osiris.

Program Initialisation

There are several ways to initialise Osiris code, each with their own use-cases. A safe and very common pattern is as follows:

  1. Make goals that contain global helper functionality top-level goals, so they are activated as soon as Osiris is initialised. Either do not rely on functionality from other goals during the initialisation of these goals, or ensure the goals on which you depend are initalised before yours:
    • they should also be top-level goals (either in your Mod, or in another Mod)
    • their name should be alphabetically sorted before your goal's name. Underscores are an often used trick to get to the front.
  1. Per level, create one goal (e.g., named the same as this level) that completes when the RegionStarted event for this level is triggered.
  2. For every quest in that level, create a sub-goal for the level goal.

We will now have a more detailed look at the various initialisation mechanics.

INIT section

The actions in an INIT section of a goal execute as soon as the goal becomes active. Its main uses are

  • Define databases used in that same goal. E.g. in a goal about crime bribes, define databases to look up the bribe range for characters of a certain level.
  • Define databases used by other goals, but related to the current goal. E.g. in a goal about a certain quest, define the dialogs of the NPCs specific to this quest.
  • Initialise the default state of objects related to the current goal. E.g. in a goal about a quest, set a character off-stage in case it should appear only at a specific point in the quest.

Direct Dependencies

If you call PROCs from your INIT code, ensure that these only depend on code from goals that are guaranteed to already have been intialised. You will not get a compile-time nor run-time error if you call a routine declared in a goal that is not yet active; the call will simply not do anything.

Example: consider the screenshot of the Goal Initialisation and Completion section. Calling a PROC defined in the FTJ_Origins_Ifan goal from the INIT code (or from a PROC called from the INIT code) of the AtaractionArtifacts goal will silently fail here. The reason is that both goals are subgoals of the _Start goal, and goals are initialised and activated in alphabetical order. So when the AtaractionArtifacts goal gets initialsed, the FTJ_Origins_Ifan goal is not yet active.

Indirect Dependencies

If you declare databases in your INIT code, or in a PROC called from your INIT code, and another goal is supposed to react to this database declaration, ensure that this other goal gets initialised before you.

Example: you define a DB_ShovelArea() fact in your goal to create a secret dirt pile. The goal that handles the setup of these secret dirt piles is __GLO_Shovel in the Shared Mod. It sets up these dirt piles by declaring event handlers such as

IF
DB_ShovelArea((TRIGGERGUID)_Trigger, (STRING)_Reward, (ITEMGUID)_DirtMound)
THEN
..

This means that if you define such a database before the __GLO_Shovel goal has been activated, nothing will happen since the event handler that reacts to this definition is not yet active. Even if the __GLO_Shovel goal initialises and activates afterwards, the above event handler will not be called for already existing DB_ShovelArea() facts. The reason is that the DB-event only fires when the DB gets defined.

Simply ensuring that your goal has a name that is alphabetically sorted after __GLO_Shovel is not sufficient in this case to ensure that the __GLO_Shovel goal has already initialised when your goal initialises. The reason is that __GLO_Shovel is a subgoal of the Shared Mod's __Start goal. This means that it will only activate when this __Start goal completes.

The Shared mod's __Start goal completes when the GameEventSet("GAMEEVENT_GameStarted") event triggers. In practice, you should seldom use this event, and rely instead on Level Events. These level events are guaranteed to trigger after the GAMEEVENT_GameStarted event. If you follow rule 3 summary above and put your DB_ShovelArea declarations in the INIT sections of subgoals of your level initialisation goals, this happens automatically.

Game Events

Level Events