Skip to content

Presets: Variables

PersonTheCat edited this page Jan 19, 2021 · 6 revisions

Getting started

On the last page, you saw a brief introduction to how variables work. On this page, we'll start from scratch and explain in detail how this system works.

Why variables

Cave Generator presets can be fairly complex for JSON files. There are a lot of settings and a lot of arrays with nested objects. There are also a lot of times when users have to declare redundant values in each object in an array. To make things worse, a few of these values are arbitrary and difficult to understand by themselves.

Variables offer a solution to every one of these problems. They also provide a sort type checking, as typing them incorrectly will throw an exception, crash your game, and tell you exactly how to solve the problem.

Declaring variables

Let's look at a fake preset which has some kind noise generator that we want to reuse in multiple locations.

someArray1: [
  {
    noise3D: {
      frequency: 0.2
      scale: 0.5
      seed: 24
    }
  }
]
someArray2: [
  {
    noise3D: {
      frequency: 0.2
      scale: 0.4
      seed: 24
    }
  }
]

It probably isn't clear until you've read everything thoroughly that you want two of these objects to share their noise. Let's introduce a variable for this noise at the top of the file to call it out and clarify what's going on.

variables: {
  EXAMPLE_SEED: 24
}
someArray1: [
  {
    noise3D: {
      frequency: 0.2
      scale: 0.5
      seed: $EXAMPLE_SEED
    }
  }
]
someArray2: [
  {
    noise3D: {
      frequency: 0.2
      scale: 0.4
      seed: $EXAMPLE_SEED
    }
  }
]

There are a few observations you should make about this process:

  • Variables in regular presets must be declared inside of variables.
  • Variables are often written in screaming snake case. This helps them stand out from regular data.
  • We can copy the value held by a variable by referring to it with a $.
  • Seeing this declaration at the top of the file implies that it has a purpose. It also gives it a name, which can help the reader understand what it's supposed to do.

Variable objects

In this example, each of our noise blocks has a different scale, so one of them will essentially spawn inside of the other. But this is a really obscure example. Let's take a look at another use for variables in which both of these noise blocks are identical.

variables: {
  EXAMPLE_NOISE: {
    frequency: 0.2
    scale: 0.5
    seed: 24
  }
}
someArray1: [
  {
    noise3D: $EXAMPLE_NOISE
  }
]
someArray2: [
  {
    noise3D: $EXAMPLE_NOISE
  }
]

Here are a few more observations after making this change:

  • A variable can hold any JSON value.
  • Variables can reduce the length of large objects, which might be easier to read.

Your variables can also refer to other variables and the order does not matter. Here's how that would look:

variables: {
  EXAMPLE_SEED: 24
  EXAMPLE_NOISE: {
    frequency: 0.2
    scale: 0.5
    seed: $EXAMPLE_SEED
  }
}

Basic imports

If you find yourself running multiple presets simultaneously, you might want to consider how those presets can share and reuse values. This will save you time when you need to make changes.

Let's create an import file and call it imports/forest_things.cave. It must be stored in the imports folder. I'll put some example data in it.

BIOMES: {
  types: FOREST
}
TUNNELS: {}

Now imagine we have a preset that wants to use this information. You can import all of this data (BIOMES and TUNNELS) by placing the file in your imports, like this:

imports: [
  forest_things.cave
]

Try to keep this information at the top of your file so that readers do not have to scan the entire file to figure out where your variables are coming from.

Now we can use these variables, like this:

imports: [
  forest_things.cave
]
biomes: $BIOMES
tunnels: $TUNNELS

Better imports

In general, presets are much easier to understand when you can see exactly where each value is coming from.

Let's import these variables individually using the :: operator. whitespace does not matter. Here's how that would look:

imports: [
  forest_things.cave::BIOMES
  forest_things.cave::TUNNELS
]
biomes: $BIOMES
tunnels: $TUNNELS

This is much easier to understand, but it takes a little more work.

The as operator

You can rename these variables to help clarify them further in the current context, if you like. Let's give these variables better names.

This can be accomplished using the as operator (as, As, or AS). As with before, whitespace does not matter, but this time, you do need some whitespace. Here's how that looks now:

imports: [
  forest_things.cave::BIOMES as FOREST_BIOMES
  forest_things.cave::TUNNELS as FOREST_TUNNELS
]
biomes: $FOREST_BIOMES
tunnels: $FOREST_TUNNELS

Whitespace in import tokens

Again, if you prefer to space things out, whitespace does not matter. Let's do that for the sake of example:

imports: [
  forest_things.cave :: BIOMES as FOREST_BIOMES
]

Merging variables

All of the previous cases we have covered involve variable substitution, wherein a reference is copied from the variables object to wherever it is used. Cave Generator provides a second use for variables, called merging. Unlike variable substitution, merging applies the contents of a variable to the current level.

Here's an example of the merge syntax in action:

variables: {
  EXAMPLE_NOISE: {
    frequency: 0.2
    scale: 0.5
    seed: 24
  }
}
caverns: {
  enabled: true
  noise3D: {
    type: PerlinFractal
    $EXAMPLE_NOISE: [ "frequency", "scale" ]
  }
}

After variable substitution is applied, here is what this preset looks like:

caverns: {
  enabled: true
  noise3D: {
    type: PerlinFractal
    frequency: 0.2
    scale: 0.5
  }
}

You can see that we have copied the fields frequency and scale out of EXAMPLE_NOISE. This expression can be read like this:

merge FREQUENCY and SCALE from EXAMPLE_NOISE

As you can see, merging copies a list of fields out of one object into another object. The source and destination must both be objects for this to work.

Merging all fields

Cave Generator provides a convenient way to copy an entire object to the current level, by using the ALL keyword. You may recall this from a previous example which uses the global variable VANILLA to apply default settings into a preset.

$VANILLA: ALL

Merge overrides

Temporary note:

When merging fields into an object, the order in which they are placed is important. Whichever field is declared last has the highest priority and is considered an override.

Take a look at this example:

variables: {
  EXAMPLE_NOISE: {
    frequency: 0.2
    scale: 0.5
    seed: 24
  }
}
caverns: {
  enabled: true
  noise3D: {
    $EXAMPLE_NOISE: ALL
    frequency: 1.0
  }
}

This operation will yield the following output:

caverns: {
  enabled: true
  noise3D: {
    frequency: 1.0
    scale: 0.5
    seed: 24
  }
}

You can see that frequency is set to 1.0 instead of 0.2 because it was defined after the merge. The same thing is true when applying multiple merges. If the value already exists when the merge happens in order, it will be overwritten.

Creating parent files

Some users have asked for the ability to declare another preset as a parent file. Here's how that can be accomplished using variables.

Let's say you want to work with underground_forest.cave, but don't want to modify the file itself. It is not possible to refer to other presets in the same directory, so your first step is to copy it into the imports folder.

Now that you have an imports/underground_forest.cave. Let's import it into the current file, as usual.

imports: [
  underground_forest.cave
]

Next, we'll apply the as operator to copy the entire file into a variable. Notice that if you don't select a specific variable to import, the as operator will copy the entire file into a new variable.

imports: [
  underground_forest.cave as PARENT_FILE
]

Now all you have to do is merge the file at the root level. Here is everything you need to do to declare a parent file:

imports: [
  underground_forest.cave as PARENT_FILE
]
$PARENT_FILE: ALL

Expanding preset variables

Now that you have your presets up and running, you might realize that individual presets now take multiple files. You may want to convert these back into single files for the following purposes:

  • To study the variable system and understand what it's doing, or
  • To create a single file that can be shared more easily.

If so, all you need to do is run /cave expand <preset> and a new file will be written in /config/cavegenerator/generated with the full, expanded contents of the original. You can also give it a new name, like this: /cave expand <preset> <name>.

And that's it! Try playing around with the variable system and seeing how you can better structure and organize your presets.

Note:

The variable system is very new. Head over to Discord if you have any feedback or suggestions for new features and changes.

Continue reading

Continue to presets: advanced tunnels

Clone this wiki locally