Tutorial 5.1 (Objects) - Aerll/rpp GitHub Wiki

About objects

In r++ objects are used to represent a collection of tiles. They can be for example a tree, a cactus, a lamp or a big unhook, essentially anything made out of more than 1 tile. They can also have any shape, which makes them very flexible.

Objects support pretty much everything that integers do. We can rotate them, insert them, create tests for them and so on. On the surface everything is simple and straightforward, but it's not really all that lovely. You need to keep in mind, that objects are very complex and they come with some limitations. We will need to dissect them a bit, in order to be able to use them correctly.

Anchor

Every object in r++ contains a special tile, called anchor. An anchor is simply the topmost and then the leftmost tile of an object.

Note: not top left!

This one tile is extremely important. Whenever we place some object on the map, in reality we only place its anchor. This is the only tile, that the automapper will use in every test we write. Luckily for us, we don't need to know the details. Just keep in mind, that the anchor is the only tile used by the automapper. Let's now look at some examples of objects and their anchors:



Ok, so we've said that the anchor is always the topmost and the leftmost tile, but what happens when we rotate an object? Let's say, that we have a 3x2 object. If we rotate it by 90° clockwise, its anchor will end up in the top right corner. But wait a minute, doesn't this mean, that the anchor is now a wrong tile? Well, it would be, but luckily r++ can handle this, so whenever we rotate an object, its anchor changes accordingly:

Other properties

One of the properties exclusive to objects, is that they can only contain unique indices and there's a good reason for that. Without this simple property, r++ would need a much more complex mechanism to cope with objects.
This property however, creates another limitation. We cannot rotate individual indices, which means, that it's impossible to let's say, create a 2x2 object using only a single corner with 4 different rotations. We can only rotate an object as a whole.

Another property, is that objects are stored in the exact same way, as they are in a tileset. What you see, is what you get. The relative position of the tiles doesn't change.

For example, if you were to create a 2x2 object out of the following tiles:


You would actually create a 3x3 object.

Creating an object

To create an object, we follow the same principles as with integers. We specify a type, which in this case is object and assign some value to it. Here a value is where it gets tricky. Fortunately, base.r provides 2 simple functions, which we can use to create a new object:

  • Indices returns an object containing given indices.
  • Rect takes a top left and a bottom right corner of a rectangle and returns an object containing all indices inside of that rectangle.

Note: objects have N rotation by default.

Let's say we want to create an object, which represents a 2x2 silver tile from generic_unhookable. Here's how we can do it:

object big1 = Indices(3, 4, 19, 20);
object big2 = Rect(3, 20); // can be also written as Rect([3, 0], [4, 1])

Both of these objects will contain tiles 3, 4, 19, 20.

InsertObject

Because objects are different than integers, we can't use Insert. Instead we will be using another function, which is identical in use, called InsertObject. We can write tests for objects with If, we can randomize them with Roll and Chance, we can prevent the automapper from generating the default test with NoDefaultPosRule and we can also insert an object at a specific position with At.

Filling objects

As we already know, whenever we insert an object, we in fact insert only its anchor. We also somehow need to place the other tiles. For that, we will use one of the predefined runs.

The run we're interested in is called FillObjects, and as the name suggests, it's simply filling objects. Here's how to use it:

object big = Indices(3, 4, 19, 20);
/*
    do stuff
*/
Run().FillObjects();

Run provides a bunch of configurable runs, which we will cover in another tutorial. Great thing about FillObjects is that it will keep track of all the objects inserted using InsertObject.

Keep in mind, that you should use this run after you're done working with objects. You need to use it at the very least per automapper:

AutoMapper("1");
/*
    do stuff
*/
Run().FillObjects();

AutoMapper("2");
/*
    do stuff
*/
Run().FillObjects();

Example

In this short example, we will do something very simple and not really practical. Our goal is to make an automapper, which will place a tree from grass_doodads tileset somewhere on our map.

We start by creating a new object, which will represent our tree. For that, we need to look at the tileset and write down all of the indices:

object tree = Indices(96_100, 112_116, 128_132, 145_146, 161_162);

Now we need to insert our tree somewhere. Let's insert a few of them. We can even mirror one:

InsertObject(tree).At([5, 5]);
InsertObject(tree).At([15, 5]);
InsertObject(tree.V).At([5, 15]);

This will place anchors at positions [5, 5], [15, 5], [5, 15].

Now that our objects are placed, we can fill them using a predefined run:

Run().FillObjects();

Full code

#include "base.r"
#output "grass_doodads.rules"

AutoMapper("Treelogy");
NewRun();

object tree = Indices(96_100, 112_116, 128_132, 145_146, 161_162);

InsertObject(tree).At([5, 5]);
InsertObject(tree).At([15, 5]);
InsertObject(tree.V).At([5, 15]);

Run().FillObjects();
⚠️ **GitHub.com Fallback** ⚠️