Open World ~ Source Document - uchicago-cs/chiventure GitHub Wiki

RPG-Openworld Source Document

Team (2020): Nicole Avila, Carolina Calderon, Chanik Bryan Lee, Eddy Rose

Team (2021): Mingyan Wang, Sumaiya Ahmed, Justin Shin

What is the purpose of RPG-Openworld?

RPG-Openworld's goal was to implement a feature that allows a room or any multiple of rooms to be automatically generated. The autogenerated room is built off randomly picked specifications stored in chiventure. These specifications are part of RPG-Openworld and we will go into more detail about how they fit in with our structs and implementation.

Modules:

In RPG-Openworld, there are 3 layers:

  • autogenerate.c
  • default_rooms.c + default_npcs.c
  • gen_structs.c + default_items.c

This layering allows for each layer above to build off the ones below limiting redundant code and creating a well-established module hierarchy. It also represents what openworld does pretty clearly. We have one module, autogenerate.c, which builds off the rest of the code to create the generation feature.

autogenerate.c

As stated before, this is the module that runs the show. It gathers room specifications from the default's, generates the room, and then puts it directly into chiventure. We will talk more about how this module functions later on. For now, it's important to realize that this file is doing the generating of rooms into chiventure.

default_rooms.c + default_npcs.c

Autogenerate doesn't actually create the info of the rooms. It needs to pull this info. That's what default_rooms and default_npcs are for. These modules hardcode values for rooms and NPCs that autogenerate can then pull from. We will talk about how these are implemented later.

gen_structs.c + default_items.c

These files are the foundation of our implementation. We need gen_structs.c to provide us with the structs that make generation work smoothly. Furthermore, without default_items.c, default_rooms would be cluttered with information. Default_rooms depends directly on default_items to provide the items' implementation for generating rooms.

Implementation:

Understanding gen_structs.c

With any new implementation there comes a time when a foundation is needed. That's what gen_structs.c provides. The structs, however, are fairly simple. There are only 3 that support the openworld feature.

roomspec_t - holds specifications/info for making a room_t in chiventure

speclist_t - a doubley linked list of roomspec_t's

gencontext_t - holds a speclist_t and other info for generation

In 2021, two structs specific to level-oriented generation were introduced:

room_level_t - associates each room (name) with a difficulty level; hashable

difficulty_level_scale_t - specifies how player lvl maps to difficulty lvl

(If you want to see how these structs are defined exactly, please visit the gen_structs.h file on the chiventure repository)

First, we need to understand what exactly a roomspec_t does. These little guys hold the info for making a room_t in chiventure without the unnecessary info. We need roomspec_t's because we are going to hardcode all of the room information. We do this because we don't generate all of the info of a room. All generation is really doing is pulling one of these guys and putting it into chiventure, but we'll talk more about how generation works later.

Now that we know what a roomspec_t is, we can take a look at the speclist_t struct, as stated previously they are doubly linked, which means you can access both the previous and next element from one element in the speclist_t. These speclist_t structs are meant to hold all of the roomspec_t's that we hardcoded. This allows the generation module to just access a speclist_t and pick a random roomspec_t.

Now, you're problably wondering. What is the purpose of the gencontext_t? The gencontext_t holds the speclist_t. That way when we pass a gencontext to the autogenerate module, it can access the speclist_t as well. The gencontext also holds additional info for generation.

Level-oriented generation, which picks and generates rooms only of the appropriate (to the player) difficulty level, needs two additional specialized structs.

First, we have room_level_t, which hardcodes the association between a room (by its unique room_name) and an integer difficulty_level. The struct is string hashable (using room_name's) to allow rapid querying by the the autogeneration algorithm (multi_room_level_generate).

The difficulty_level integer is not to be confused with the player_level integer that is defined in the player_t struct. While player_level is a measure of player stats/strength, difficulty_level gauges the strength of player obstacles like challenging rooms, hostile NPCs etc. We use difficulty_level information for level-oriented generation. Since there is no reason to assume that the two level types are equivalent, we created a new struct called difficulty_level_scale_t in order to hardcode mappings from player_level to difficulty_level.

default_items.c

This module just holds the necessary info for generating a roomspec in default_rooms. Each room needs a set of items, so these values needed to be stored somewhere.

get_default_items() - call this function to access the default list of items.

(You can read more about what this function does here at the default_items.h file on the chiventure repository)

Anyone on chiventure can use this module if they would like to have access to a list of default items. However, its first and foremost purpose is for the default_rooms module which we will get into next.

Hardcoding rooms, items, and NPCs

As we mentioned earlier, we need to have hardcoded values in order for the generation to work. We saw what default_items does and default_rooms and npcs builds off of that.

default_rooms.c

Now, we need to build the rooms since we have the items. Default_rooms.c does that with two functions:

copy_item_to_hash(item_hash_t **dst, item_hash_t *src, char *name);

make_default_room(char *bucket, char *sh_desc, char *l_desc);

These rooms are then sorted based on a "char *bucket." When given an item, they are assigned it through the copy_item_to_hash function. These functions work hand-in-hand to create the lists and organize them properly. In the future, these may be different, but for now, it is this way.

Autogenerating...

With all the foundation understood, we now can dive into the aspects of how we are generating rooms directly into chiventure. There are three (fundamental) functions that we need to look at that make the generation possible.

roomspec_to_room(roomspec_t *roomspec);

room_generate(game_t *game, gencontext_t *gencontext, roomspec_t *rspec);

multi_room_generate(game_t *game, gencontext_t *context, char *room_id, int num_rooms);

Now, let's say we call room_generate. We give it a gencontext_t and some roomspec_t for generation. We call the roomspec_to_room to change this roomspec into a room that we can then append to chiventure. We attach this newly made room to the game->curr_room of chiventure (you can read more about the game struct at the game.h file in the chiventure repository). This all may be a bit confusing so here's a nice little diagram of how this all works.

Autogenerating - Level-Oriented Generation...

A helper called filter_speclist_with_difficulty filters a given speclist_t so that it only contains roomspec_t's of a given difficulty level.

speclist_t* filter_speclist_with_difficulty(speclist_t *speclist, room_level_t **room_levels, int difficulty_level);

Next, we have the function responsible for level-oriented generation, multi_room_level_generate...

int multi_room_level_generate(game_t *game, gencontext_t *context, char *room_id, int num_rooms, room_level_t **room_levels, difficulty_level_scale_t *level_scale);

...which simply combines the functionality of filter_speclist_with_difficulty and multi_room_generate. It obtains a filtered speclist_t by calling the first, creates a new gencontext_t containing it, then calls multi_room_generate with this new gencontext_t to load rooms (of appropriate difficulty) into the game.

Team (2022):

Implementation:

specgraph_t

In 2022, we built off of the previous year's implementation mainly through updating the speclist_t struct to a specgrah_t. This new struct converted the previous struct, which was a linked list, to a directed graph. Each graph contains an adjacency matrix of "edges", which, on a scale of one to five, show how likely rooms of different specifications are to generate next to each other. For example, this attribute would prevent libraries from spawning next to dungeons. The specgraph_t struct should be valuable when coming to dynamic in-game autogeneration.

coords_t

We also finished creating a new coords_t struct. This struct would give a coordinate-type location to each room. With this, it will be easy to check if there are surrounding rooms in the vicinity of the current one, allowing the game to potentially generate a path between the two rooms.

Unfinished Business

There were a few areas that we started work on, but did not quite get to implement. For example, we are currently working on various autogeneration functions for both dynamic and static autogeneration of rooms, though it is unlikely we will have those implemented by the end of the year. Those functions can be found on the branch openworld/game_autogeneration.

In addition, we also began working on making openworld WDL compatible, though this will also likely not get implemented by the end of the quarter. This is one area that we strongly urge future teams to work on immediately, as it will be key when it comes to integrating openworld to the game.