Stashing treasure¶
In this chapter we’ll use the optional DropTable
component to implement chests and have
kobolds drop items.
Getting the drop on it¶
Head over to main.lua
and load the drop table module.
prism.loadModule("prism/extra/droptable")
Now we can give modules/game/actors/kobold.lua
a DropTable
. We’ll give them a 30% chance to
drop one of our meat bricks.
prism.components.DropTable{
chance = 0.3,
entry = prism.actors.MeatBrick,
}
If you were to kick a few kobolds you’d notice nothing is happening! That’s because the drop table
needs to be hooked into the game logic. Open modules/game/actions/die.lua
and we’ll add drops to
the level when an actor dies.
function Die:perform(level)
local x, y = self.owner:getPosition():decompose()
local dropTable = self.owner:get(prism.components.DropTable)
if dropTable then
local drops = dropTable:getDrops(level.rng)
for _, drop in ipairs(drops) do
level:addActor(drop, x, y)
end
end
-- rest of Die:perform
end
If they have a drop table, we use DropTable:getDrops()
to roll the drop table, and add
each item to the level at the actor’s position.
To ensure dropped items don’t float over pits, we can add the System:onActorAdded()
callback to modules/game/systems/fallsystem.lua
.
function FallSystem:onActorAdded(level, actor)
level:tryPerform(prism.actions.Fall(actor))
end
Boot up the game and kick a few kobolds around. They should start dropping meat!
Creating containers¶
For chests, we’ll start with a new tag. Navigate modules/game/components
and create a new file
there called container.lua
.
--- @class Container : Component
--- @overload fun(): Container
local Container = prism.Component:extend "Container"
function Container:getRequirements()
return prism.components.Inventory
end
return Container
Next we’ll define a new action for opening these. Head over to modules/game/actions
and create a
new file called opencontainer.lua
. For our target, we want a container within range 1 that we
can see.
local Name = prism.components.Name
local Log = prism.components.Log
local OpenContainerTarget = prism.Target()
:with(prism.components.Container)
:range(1)
:sensed()
In perform
, we grab every item in the target container’s inventory and dump them on the ground,
before removing the container and logging messages.
--- @class OpenContainer : Action
local OpenContainer = prism.Action:extend "OpenContainer"
OpenContainer.targets = { OpenContainerTarget }
OpenContainer.name = "Open"
--- @param level Level
--- @param container Actor
function OpenContainer:perform(level, container)
local inventory = container:expect(prism.components.Inventory)
local x, y = container:expectPosition():decompose()
inventory:query():each(function(item)
inventory:removeItem(item)
level:addActor(item, x, y)
end)
level:removeActor(container)
local containerName = Name.get(container)
Log.addMessage(self.owner, "You kick open the %s.", containerName)
Log.addMessageSensed(level, self, "The %s kicks open the %s.", Name.get(self.owner), containerName)
end
return OpenContainer
Now that we’re all set up with our container logic we need to actually make a container to try this
with. Let’s create a new file in modules/game/actors
called chest.lua
. We’ll accept a
contents
parameter to define the items in the chest.
prism.registerActor("Chest", function(contents)
--- @cast contents Actor[]
return prism.Actor.fromComponents {
prism.components.Name("Chest"),
prism.components.Position(),
prism.components.Inventory{items = contents},
prism.components.Drawable("(", prism.Color4.YELLOW),
prism.components.Container(),
prism.components.Collider()
}
end)
Note
To support Geometer, parameters passed to actor and cell factories must be optional.
Cracking a cold one¶
If you launch the game and bump into a chest you’ll notice you kick it, which is fun but not exactly
what we want. We’ll have to change to logic in GameLevelState
. In
modules/gamestates/gamelevelstate.lua
GameLevelState:keypressed
and add the following right
above where we try to kick:
function GameLevelState:keypressed(key, scancode)
-- yada yada
if keybindOffsets[action] then
-- blah blah
local openable = self.level
:query(prism.components.Container)
:at(destination:decompose())
:first()
local openContainer = prism.actions.OpenContainer(owner, openable)
if self.level:canPerform(openContainer) then
decision:setAction(openContainer)
return
end
-- kick stuff
end
end
Okay! When you walk into a chest now you should pop that sucker open! Congratulations! Wait, nothing was inside the chest though. That’s not very fun. Let’s take care of that.
Spicing up level generation¶
Let’s first create a new top level folder, loot
and within that folder a new file chest.lua
.
Let’s keep it simple for now and give chests a guaranteed drop of meat.
return {
{
entry = prism.actors.MeatBrick
}
}
At the end of levelgen.lua
, we’ll spawn a chest in the middle of a random room.
local chestRoom = availableRooms[rng:random(1, #availableRooms)]
local center = chestRoom:center():floor()
local drops = prism.components.DropTable(chestloot):getDrops(rng)
builder:addActor(prism.actors.Chest(drops), center:decompose())
return builder
The chest will overlap with a kobold, but we’ll deal with that when we revisit level generation. You’ll see now that when we open the chest we get a meat brick!
In the next chapter¶
We’ve used the DropTable
component to add drops to kobolds and added chests. In the
next chapter we’ll add a potion and go over making buffs.