04. Placing things in rooms¶
Now we know how to make more rooms than we probably need, but they're all empty and that's pretty boring. For testing, we'll start with this generator, which is a toned-down version of one from the previous part:
local libLevelGen = require "LibLevelGen.LibLevelGen"
local segment = require "LibLevelGen.Segment"
local room = require "LibLevelGen.Room"
local roomGenCombinations = segment.createRandLinkedRoomParameterCombinations {
direction = {room.Direction.UP, room.Direction.DOWN, room.Direction.LEFT, room.Direction.RIGHT},
corridorEntrance = {0.25, 0.5, 0.75},
corridorExit = {0.25, 0.5, 0.75},
corridorThickness = {3},
corridorLength = {0, 1, 2},
roomWidth = {6, 7, 8, 9},
roomHeight = {6, 7, 8, 9},
}
local function createTwoRooms(currentRoom, roomsLeft)
if roomsLeft > 0 then
local newCorridor1, newRoom1 = currentRoom.segment:createRandLinkedRoom(currentRoom, false, roomGenCombinations)
local newCorridor2, newRoom2 = currentRoom.segment:createRandLinkedRoom(currentRoom, false, roomGenCombinations)
-- Check if the generation didn't fail:
if newRoom1 then
createTwoRooms(newRoom1, roomsLeft - 1)
end
if newRoom2 then
createTwoRooms(newRoom2, roomsLeft - 1)
end
end
end
local function myGenerator(genParams)
local instance = libLevelGen.new(genParams)
local mainSegment = instance:createSegment()
local startingRoom = mainSegment:createStartingRoom()
createTwoRooms(startingRoom, 2)
instance:finalize()
end
libLevelGen.registerGenerator("LibLevelGen Playground", myGenerator)
Room types¶
As mentioned earlier, a "room" in LibLevelGen is a quite wide term - it applies to actual rooms, corridors, secret rooms, vaults and even secret shops! Because of this, we need a way to know which operations we can perform in which room - and this is where room flags come into play.
If you check out the link above, you'll see that there's a lot of flags like ALLOW_ENEMY, ALLOW_SHRINE, EXIT and more. These determine what kind of things can be done to the room. For example, an exit room will have ALLOW_ENEMY but not ALLOW_SHRINE, because shrines don't spawn in exit rooms.
There's also a second enum, room types, which are just collections of flag. For example, room.Type.EXIT consists of ALLOW_ENEMY, ALLOW_TRAVELRUNE, ALLOW_TORCH, ALLOW_TILE_CONVERSION and EXIT.
When creating rooms with createRandLinkedRoom, the corridors get flags set to room.Type.CORRIDOR and the actual rooms to room.Type.REGULAR. Of course, you can then modify the flags to your liking, but these are generally pretty good defaults.
There's a method to select all rooms which match given flags: Segment:selectRooms, though often it might be more convenient to use Segment:iterateRooms, which will automatically call the given function for every matching room.
Placing entities!¶
Let's run some code for every room which allows enemies, like so:
local function placeEnemies(currentRoom)
dbg("Hello from room at ", currentRoom.x, currentRoom.y)
-- room.Flag.inspect is a useful debug method that exists for
-- every enum automatically.
dbg("My flags are: ", room.Flag.inspect(currentRoom.flags))
end
local function myGenerator(genParams)
local instance = libLevelGen.new(genParams)
local mainSegment = instance:createSegment()
local startingRoom = mainSegment:createStartingRoom()
createTwoRooms(startingRoom, 2)
mainSegment:iterateRooms(room.Flag.ALLOW_ENEMY, placeEnemies)
instance:finalize()
end
placeEnemies for every non-corridor room except the starting room, which is where we definitely do not want any enemies. Now, let's place some entities in the room: this can be done using a handful of methods, but all of them call Tile:placeEntity in the end:This will create a little Skeleton friend in the top-left corner of every room where enemies are allowed. Now, it'd be nicer to place the enemy randomly, and there's actually an entire system for selecting tiles which we'll talk about in the next part. For now, let's just place the skeleton in a corner.
A quick reminder though: this does not actually create an actual entity yet, it creates a LibLevelGen Entity object which will then be used to construct the data needed to spawn the entity by the game.
But room:getTile(...):placeEntity(...) is a lot of typing, so there's a convenience method to do this with a few less characters:
Entity levels¶
So in the base game, different entity levels are defined by just appending a number to the entity name: like Skeleton, Skeleton2, Skeleton3 and so on. But LibLevelGen actually handles it a bit differently - the level and the type are separate arguments to placeEntity methods.
Skeleton3 will also work, LibLevelGen will not actually know that it's a Skeleton - it will interpret this as an entity with base type called Skeleton3 and level 1. We'll talk more about enemy upgrades and registering your own, custom entity types in the future.
Placing torches¶
Our level could really use some more light, so let's add some torches to it. We could iterate over rooms that have the ALLOW_TORCH flag, but there's actually a convenience function to place a given amount of torches per room:
Making an exit room¶
Our level is still missing a pretty crucial part - a way to actually leave it. So first, we need to pick a room that we'll make into an exit room, and ideally it should not be directly adjacent to the starting room. We can do that with modyfing the createTwoRooms function in a clever way:
local function createExit(currentRoom)
dbg("Create an exit room here!")
end
local function createTwoRooms(currentRoom, roomsLeft, needsExit)
if roomsLeft > 0 then
local newCorridor1, newRoom1 = currentRoom.segment:createRandLinkedRoom(currentRoom, false, roomGenCombinations)
local newCorridor2, newRoom2 = currentRoom.segment:createRandLinkedRoom(currentRoom, false, roomGenCombinations)
-- Check if the generation didn't fail:
if newRoom1 then
createTwoRooms(newRoom1, roomsLeft - 1, needsExit)
end
if newRoom2 then
createTwoRooms(newRoom2, roomsLeft - 1, false)
end
elseif needsExit then
createExit(currentRoom)
end
end
Essentially, the
needsExit parameter will only remain true for one of the last rooms generated, so even if we were to change 2 to 5 when calling createTwoRooms we'll still get only one exit room. Of course, you can do this in any other way you'd like.
Now, how do we make this room an exit room?
Aaaand that's it, this function will do everything for us. As the documentation states:Turn the room into an exit room - set appropriate flags, place the exit stairs and the miniboss.
We just need to specify which minibosses it can pick from. It even makes sure that the same miniboss type won't appear 2 floors in a row (unless there's no other choice), and also makes sure to spawn 2 minibosses when Shrine of Boss is active. Wowie!
Our level is slowly getting better! In the next part, we'll learn the tile selection system briefly mentioned here.