This is one of the core modules, providing the low level classes essential for role playing games. As with all parts of the engine, the aim is to provide a basic set of features that can be tailored to individual rule sets by extending them with Python scripts. That is how Adonthell's Role Playing System will be implemented.
Quests and Logs
To represent the plot and record the playerâ€™s progress in the game, the quest system has been written. It allows for defining milestones of quests that can be queried and changed by various Python scripts, like dialogues, event callbacks or character schedules. Whenever the state of a quest changes, these scripts can make the necessary changes to the gameworld.
As quests are usually not linear and often solvable in multiple ways, each quest is represented by a tree, whose root is the quest itself and whose leaves represent its individual milestones. These milestones are the only part of a quest that can be directly influenced by scripts â€“ they can be set to completed. All the quest nodes below the leaves have a rule how to determine their state of completion from the state of their children. Scripts that only need to know whether a quest as whole is completed can query the state of the quest root, while scripts that need more detailed information can query nodes higher up the tree.
Queries are not limited to the two states complete and incomplete though. The engine distinugishes between the following:
- started A quest is started as soon as its first milestone is set to completed. (As milestones are atomic, starting them means to complete them)
- in progress A quest that has been started but not yet completed.
- completed Once the player has accomplished a quest or part thereof, it is set to completed (in which case it is no longer in progress).
What has been described so far allows the engine to keep track of progress through the game. To help players keeping track, a quest log has been implemented too. This is closely tied to the quest tree, as each node of the tree can have log entries attached that will be copied into the quest log whenever that node is set to started and/or completed.
The log system itself supports several log books; but so far, the quest log is the only one integrated into the engine. Others may be implemented by individual games. In order to ease navigation, the engine provieds support for automatic index generation: a list of keywords can be specified and whenever an entry is copied into a log book, its text is searched for those keywords and an index entry is created under each keyword found in the text. Additionally, an entry can provide additional keywords, so that index entries can also be created for keys not found literally in the text.
On top of the indexing mechanism, a fulltext search could be easily implemented too. All it takes is creating a new index with the search terms as its only keywords. Then all entries of the desired log books need to be processed by that index, resulting in a list of index entries for each keyword. By limiting the search result to entries appearing in all lists, one will find the log entries containing all the search terms. More complex boolean search operators could be supported by processing the resulting lists accordingly.
Even though the index is optimized for comparing text against a long list of keywords, a fulltext search done that way should still be pretty fast.
Items and Inventories
In this section, we will explore how items are modelled and kept. The main goal was to allow for all kinds of items, with many different abilities. Creation of new items should be fast and simple, without having to modify the engine. And last but not least, the item system as a whole should be efficient and convenient for both designers and players.
Therefore, items have been splitted into three layers.
- Engine layer. Functionality required to manage items is implemented by the engine. This includes item instantiation, inventory handling and access to advanced item functionality.
- Extension layer. The item system itself â€“ properties and abilities as well as game rules concerning items â€“ is implemented in Python on top of these basic functions. That way, different games can have totally different item systems.
- Data layer. On top of the item system sits the item data, i.e. all the individual items actually available in a game.
The figure shows the relation between extension, data and engine layer. For each â€œclassâ€ of items (such as weapons, books, potions, etc), a template needs to be written in Python. By initialising attributes of the same template with different values, different items can be defined. At runtime, multiple instances of every such item can be created and used by the engine.
For efficiency reasons, the engine distinguishes between two different kinds of items:
- Immutable items are instantiated only once. They are kept in a central item_storage and reference counts are used to allow multiple occurrences in the game. As a result, immutable items cannot change their properties. If they would, suddenly all those items would change in the same way. Canditates for immutable items are coins, books, various ingredients ...
- mutable items on the other hand require a new instance for every occurrence in the game. As a result, their properties can be easily changed throughout the game without affecting other items of the same kind. Canditates for mutable items are armour and weapons that might fall into a state of disrepair after frequent use.
To keep memory usage down, it would be good to make as many items as possible immutable. This may sound restrictive, but with a little trick, even immutable items can change: this is done by transforming the item. For example, if a character applies poison to an arrow, we do not change a property of that arrow, instead we say this item is now a poisoned arrow. Both arrow and poisoned arrow can be immutable items for the engine, while to players it appears as if the arrow has changed its properties.
Despite the fact that large parts of the item system are not implemented by the game engine, items are always stored on engine level, in so called slots. A slot may contain several items of the same kind, if the item is stackable. Immutable items are considered to be of a kind when their instance pointers are equal. For mutable items, item names have to match.
Inventories are not limited to a certain number of slots. If desired, they can even grow or shrink at runtime. Therefore, inventories can be used whenever large quantities of items need to be stored, not only to represent a characterâ€™s backpack.
Characters and Equipment
One special use of the inventory class is to store a character's equipment, i.e. the items a character wears and carries on his body, like armour, weapons, rings or tattoos. To help with equipment management, the equipment class has been written. It allows to define equipment slots together with the item categories they can contain and a modifier value. Only one type of modifier is possible right now, (which will be used to implement a coverage value for armour), but the design might be extended to allow several modifiers that would be referenced by userdefined keywords.
Apart from equipment slots, the equipment class also allows the definition of equipment sets, i.e. a character-, race- or faction specific combination of equipment slots. Equipment inventories can then be automatically created for these sets and modifier totals can be computed.