Earlier I blogged about procedural lore generation. That in itself is very large and complex topic and I wouldn’t want to tackle that in one go. One major part of the system would be generating all kinds of artefacts, of which some could even apppear in the game. This is quite large part too and will require significant changes (I have very few item types implemented currently).
But, I can already get started on fleshing out my ideas and implement a tiny, fairly insignificant portion of the system and see how well it will work and what kind of new ideas I’ll unearth. For this I chose cosmetic (at least for now) scrolls that player can find laying around everywhere in the game. I won’t be touching that much how the scroll generating will actually be implemented as a part of item system. Instead, I’ll assume that there’s some sensible way of system to call the code (although, I do have some hazy ideas already) and trigger creation of a scroll.
Currently scrolls are all alike and differ only by their text. They’re defined in tomes.hy and use simple dsl:
(scroll "Curious scroll I" "The butcher with the sharpest knife has the warmest heart.")
Definition of scroll is rather straightforward too:
(defn item [name description cost weight icons types rarity] (ItemConfiguration :name name :cost cost :weight weight :icons icons :types types :rarity rarity :description description)) (defmacro def-scroll [name cost rarity] `(defn ~name [name &rest content] (item name (.join " " content) ~cost 1 ["tied-scroll"] ["scroll" "hint"] ~rarity))) (def-scroll scroll 50 "uncommon")
So any scroll is defined as a ItemConfiguration object that records what a specific type of scroll looks like. The values are read when game starts up and loads configuration scripts. After that point, all instances of specific item will have same attributes with same values.
But with the new system, we would like to define a set of properties, their value space and have a function that uses that data to generate new scrolls with new values of attributes for us on demand. And if we ask the system to generate us the same scroll, it should generate exactly identical instance. This is important if we want to be able to refer to specific items in generated lore and have same items to show up in the game. Interface to the system is quite plain:
(defn generate-scroll [&optional [entity-id nil]] ...)
entity-id is id of the generated scroll. If it’s not provided, system will pick one in random and generate scroll based on the id. When specified, it will be used as the seed for pseudo-random number generator, which in turn ensures that same scroll is generated with identical attributes.
Below are three example that I came up as what I would like to generate:
Litanies of Mercy
Finely detailed brass scroll tube containing litanies of mercy. Parchment is very fragile and writing has faded in some places.
Scriptures of Augustina
Plain wooden scroll tube containing scriptures of Augustina. Vellum is very well preserved and mastercraft writing is very ornate. Seal has been attached to scriptures.
Prayer of Maximus
Ornate onyx scroll tube with two silver reliefs containing prayer of Maximus. Parchment is rather old and good quality writing is very beautiful. Silver sigil of Venice has been attached to prayer.
As mentioned earlier, differences between scrolls are mostly cosmetic at this point and only serve to add a little bit of flavour to the game.
It would be possible to just generate standard scrolls and have their name and description to reflect what out generate-scroll method decided to spit out this time. But I have (fuzzy) plans where it would be good for computer to know exact parameters of the generated artefacts, so I’ll rather also store the actual attributes in computer readable form. This will probably be something generic like a dictionary, which allows me to handle various kinds of artefacts without having to modify the containing data structure. That of course would place burden of remembering names of keys on the person writing the client code, so a set of functions for setting and querying attributes of specific type of artefact should also be created.
After analyzing three examples above, I came up with following set of attributes and their example values:
name: form: litanies, scriptures, prayer type: mercy, Augustina, Maximus tube: tube-qualifier: finely detailed, plain, ornate tube-material: brass, wooden, onyx scroll: paper-type: parchment, vellum paper-qualifier: very fragile, very well preserved, rather old writing-qualifier: faded, mastercraft, good quality writing-detailer: very ornate, very beautiful paper-detailer: seal, silver sigil of Venice
I could come up with code that had access to list of possible attribute values and it would then pick one randomly, but I have something more interesting in my mind. Augustina, Maximus ans Venice are all names. These names could in theory refer to something in the generated lore. In these cases the generator will either call name generator (I blogged about one that uses markov chains earlier) or generator to generate actual entity and then picks appropriate attributes from that. This way I can have minor celebrities in lore, who actually had impact on the story: “sure, this town was founded by sage Valerius and that temple over there houses pinky finger bone of sage Valeris, our most prized relic.” I’m hoping that having things to interconnect and refer to each other will help to alleviate the fact that the content is just the same old same with different names.
So when the time comes for generate-scroll to create a new scroll, the algorithm would randomly select some attributes from predefined lists, while some attributes might result from another generator being run (that generator would get seed from generate-scroll, ensuring that same item can be generated again and again if needed). Final step of generating a new scroll would be reading the artefact datasctructure and creating human readable name and description of the item. The last step is the hardest one to get right and I’m expecting that I have to get back to it multiple times as I fine tune the system.
There are plenty of open questions still. For example, I’m not completely sure how hierarchy of generated attributes should be represented or where the decision of how attributes are translated to human readable text is done. But those are just details that will get ironed over the time.