07. A variety of small things #1¶
So generally, the most important things have been discussed in the previous part already. Yay!
Still, that doesn't mean that there's nothing left to talk about - in fact, there's a whole bunch of small but neat things!
Randomness¶
So far, the randomness in our level has been determined by built-in functions, but that doesn't mean we can't generate random numbers ourselves. The code we'll start with is this.
Let's try randomizing the levels of the Skeletons we spawn: we'll use instance:randIntRange for this.
You should not use the game's built-in RNG module for level generation in LibLevelGen! While it will work, it will make the levels different even on the same seed, unless you specifically set up proper RNG channels youself.
local function placeEnemies(currentRoom)
-- For convenience, objects have a reference to the instance they belong to.
local instance = currentRoom.instance
-- randIntRange is not inclusive for the 2nd number, so we get numbers from 1 to 3 here.
currentRoom:placeEntityRand(tr.Enemy.Generic, "Skeleton", instance:randIntRange(1, 4))
currentRoom:placeEntityRand(tr.Enemy.OnWall, "Spider")
end
Pretty simple so far. Another function we can use is instance:randChoice:
local function placeEnemies(currentRoom)
local instance = currentRoom.instance
currentRoom:placeEntityRand(tr.Enemy.Generic, "Skeleton", instance:randIntRange(1, 4))
currentRoom:placeEntityRand(tr.Enemy.OnWall, "Spider")
local enemyType = instance:randChoice {"Monkey", "Bat", "Ghost"}
currentRoom:placeEntityRand(tr.Enemy.Generic, enemyType)
end
instance:randChance, which returns a boolean:
local function placeEnemies(currentRoom)
local instance = currentRoom.instance
currentRoom:placeEntityRand(tr.Enemy.Generic, "Skeleton", instance:randIntRange(1, 4))
currentRoom:placeEntityRand(tr.Enemy.OnWall, "Spider")
local enemyType = instance:randChoice {"Monkey", "Bat", "Ghost"}
currentRoom:placeEntityRand(tr.Enemy.Generic, enemyType)
if instance:randChance(0.5) then
currentRoom:placeEntityRand(tr.Enemy.MovingSlime, "Slime", 2)
end
end
tr.Enemy.MovingSlime is used here - it's essentialy the same as Generic, but does not place the entity next to a wall. After all, our Slime would be pretty sad if we spawned it in a way that makes it unable to move, and we do not want to make the Slime sad.
Exit rooms typically have extra enemies, so we can throw in some extra ones too:
local function placeEnemies(currentRoom)
local instance = currentRoom.instance
currentRoom:placeEntityRand(tr.Enemy.Generic, "Skeleton", instance:randIntRange(1, 4))
currentRoom:placeEntityRand(tr.Enemy.OnWall, "Spider")
local enemyType = instance:randChoice {"Monkey", "Bat", "Ghost"}
currentRoom:placeEntityRand(tr.Enemy.Generic, enemyType)
if instance:randChance(0.5) then
currentRoom:placeEntityRand(tr.Enemy.MovingSlime, "Slime", 2)
end
if currentRoom:checkFlags(room.Flag.EXIT) then
currentRoom:placeEntityRand(tr.Enemy.Generic, "Monkey", 2)
currentRoom:placeEntityRand(tr.Enemy.Generic, "Slime")
end
end
placeEnemies function for now.
Acquiring entity names¶
Okay, this section is a bit of a detour, but it's worth talking about. So far the entities we placed had fairly intuitive names, but it might not always be the case so it's worth knowing how to find the name of the entity you want. The built-in level editor is of great help here - and you can actually enable it mid-run!
To enable the level editor for live-editing, open the menu and go to Customize -> Downloadable Content. Then, use the icon shown below to enable display of individual feature packs:

Once you switch this option to "on", the feature packs will be displayed:

And you can activate the level editor! After doing that, exit the menus and right-click anywhere in the level to open the editor (and press escape to close it):

But we're not done yet - to show internal entity names, we need to activate this option in the editor settings:

And now finally, we can search for entities. To toggle the category list in the right panel, either press [Tab] on the keyboard or right click that panel. For example, let's go to the Traps category and check the entity name of the all-directional bounce trap:

There's a small subtitle telling us that it's called BounceTrapOmni when we hover over it. Cool!
The level editor can also be used to preview the level sequence if you go to Dungeon Settings (the Stairs icon in the toolbar), which may be useful for debugging and skipping to given levels.
Placing traps¶
Traps are placed in the same way as enemies - let's go ahead and create a placeTraps function:
-- in myGenerator function, below the iterateRooms call that calls placeEnemies...
mainSegment:iterateRooms(room.Flag.ALLOW_TRAP, placeTraps)
-- above the myGenerator function
local function placeTraps(currentRoom)
local instance = currentRoom.instance
end
ALLOW_ENEMY, the ALLOW_TRAP flag can be used to check whether the room is suitable for trap placement. In this case it's similar to enemies, but the exit room will not have this flag set because exit rooms normally do not have traps. Either way, time to place some traps; the entity names can be acquired with the method explained above.
local function placeTraps(currentRoom)
local instance = currentRoom.instance
local trapTypes = {"BombTrap", "BounceTrapOmni", "SpikeTrap", "Sync_DiceTrap"}
local trapCount = instance:randIntRange(1, 4)
for i = 1, trapCount do
currentRoom:placeEntityRand(tr.Trap.Generic, instance:randChoice(trapTypes))
end
end
Making later floors harder¶
This is actually pretty simple to do - the instance:getFloor() method returns the current floor number, and you can use it to determine various things - for example, let's head back to placeEnemies for a moment and modify it a bit:
local function placeEnemies(currentRoom)
local instance = currentRoom.instance
-- !
local floor = instance:getFloor()
-- !
currentRoom:placeEntityRand(tr.Enemy.Generic, "Skeleton", instance:randIntRange(math.min(floor, 3), 4))
currentRoom:placeEntityRand(tr.Enemy.OnWall, "Spider")
local enemyType = instance:randChoice {"Monkey", "Bat", "Ghost"}
currentRoom:placeEntityRand(tr.Enemy.Generic, enemyType)
-- !
if instance:randChance(0.5) or floor > 2 then
currentRoom:placeEntityRand(tr.Enemy.MovingSlime, "Slime", 2)
end
-- !
if floor > 1 then
currentRoom:placeEntityRand(tr.Enemy.Generic, "Bat", 2)
end
if currentRoom:checkFlags(room.Flag.EXIT) then
currentRoom:placeEntityRand(tr.Enemy.Generic, "Monkey", 2)
currentRoom:placeEntityRand(tr.Enemy.Generic, "Slime")
end
end
! comment. While changing just the enemies is pretty basic, this is just an example - there is a whole lot more you can do across the entire level!
A bunch of automatic things¶
LibLevelGen has a few quite useful options that you can enable: let's add a little thing to our generator configuration...
libLevelGen.registerGenerator("LibLevelGen Playground", myGenerator, {
placeShrines = true, -- !!!
levelsPerZone = 4,
zones = 4,
sequenceClb = levelSequence.templateAllZones()
})
Note: this feature had a bug in versions 1.0.0 and 1.1.0, update to 1.1.1 or later should issues occur.
The same can be done with secret shops:
libLevelGen.registerGenerator("LibLevelGen Playground", myGenerator, {
placeShrines = true,
placeSecretShops = true, -- !!!
levelsPerZone = 4,
zones = 4,
sequenceClb = levelSequence.templateAllZones()
})
You can even define your own shrines and shop types to be placed, but we'll talk about it later (since it's a bit more advanced).
There's more things like this, but I think this part is long enough by now - to be continued...