More on providing control over placing things in the Dungeon. I’m thinking about changing my mind, but I might change my mind about that. (Spoiler:Bear bites man.)

In playing the game, I realized that I rather liked the way that when you bumped a Chest it would open and display the treasure within. Then you’d bump it again and get the treasure. I removed that capability yesterday, and I think I’d like to have it back. In fact, I think I’d like the Decor to work the same way, bump them, you get poisoned or whatever, and any hidden treasure appears.

It is tempted to combine Chest and Decor, since I want them to be “the same”, but somehow I am smart enough to decide, instead, to do the one, do the other, and only then see what convergence of the code might be done.

Why? Because smaller steps are always better. What, always? Yes, I think always. The only downside to small steps is more frequent commits, which take literally precious seconds, so not a seriously big deal. And one could even “save time” by skipping some intermediate commits, but we’re not going to try that here. Might forget, but not gonna do that on purpose.

Small steps. No, smaller than that.

Make Chest Show Contents

There are a few interesting issues here. One is that we need to be concerned about the z-level of the items, so that they will display on top of their container, be that a Chest or a barrel or a skull. I think that is likely already built in but we’ll want to be sure. Another issue is that the items have messages, so we’ll want to be sure that those work properly. Finally, there is the issue of “values” in the treasures, that is, the number of health points or strength points or whatever they provide. We will probably want our Level Design Corporation to be able to have specific control over those. We may not provide that ability today, but since we’re passing through, let’s be sure that we can do it.

I happen to remember how the Chest used to do this trick. When it was opened, it would create a Loot containing the health InventoryItem, and place it on its own tile. I can even find that code, either in a preceding article or in my Git repo.

Let’s get started. Here’s the relevant code in Chest:

function Chest:open()
    if self:isOpen() then return end
    self.opened = true
    self.pic = self.openPic
    self.item:addToInventory()
end

How do we create a Loot?

Loot = class(DungeonObject)

local LootIcons = {strength="blue_pack", health="red_vial", speed="green_flask",
pathfinder="blue_jar", curePoison="red_vase"}

local LootDescriptions = {strength="Pack of Steroidal Strength Powder", health="Potent Potion of Health", speed="Potion of Monstrous Speed" }

function Loot:init(tile, kind, min, max)
    self.kind = kind
    self.icon = self:getIcon(self.kind)
    self.min = min
    self.max = max
    self.desc = LootDescriptions[self.kind] or self.kind
    self.message = "I am a valuable "..self.desc
    if tile then tile:moveObject(self) end
end

Ah, this is interesting. Not every InventoryItem can be a Loot, it would appear. When the Loot is bumped, we do this:

function Loot:actionWith(aPlayer)
    local item
    self:currentTile():removeContents(self)
    item = self:createInventoryItem(aPlayer)
    item:addToInventory()
end

function Loot:createInventoryItem(aPlayer)
    return InventoryItem(self.kind, math.random(self.min,self.max))
end

We roll the random number between min and max as we create the item, then add it to inventory. I think you’re not informed about the value.

First mistake? Probably shouldn’t have put this off.

Surely we want any InventoryItem to be used as Loot. But let’s defer that concern and put the Chest back to its old form. I think we’re going to have an issue with min and max, which was wired into Chest before:

    return Loot(self:currentTile(), "health", 4,10) -- returned for testing

As we see there, we made the determination of type and values right there, and the name is looked up in the Loot’s table show above.

We want a new method on Loot, fromInventoryItem. Let’s code that Intention.

function Chest:open()
    if self:isOpen() then return end
    self.opened = true
    self.pic = self.openPic
    Loot:fromInventoryItem(self:currentTile(), self.item)
end

Now we need to code that method. How do InventoryItems get defined?

local ItemTable = {
    pathfinder={ icon="blue_jar", name="Magic Jar", attribute="spawnPathfinder", description="Magic Jar to create a Pathfinding Cloud Creature" },
    rock={ icon="rock", name="Rock", attribute="dullSharpness", description="Mysterious Rock of Dullness", used="You have mysteriously dulled all the sharp objects near by." },
    curePoison={ icon="red_vase", name="Poison Antidote", attribute="curePoison" },
    health={icon="red_vial", name="Health", description="Potent Potion of Health", attribute="addPoints", value1="Health", value2=1},
    strength={icon="blue_pack", name="Strength", description="Pack of Steroidal Strength Powder", attribute="addPoints", value1="Strength", value2=1},
    speed={icon="green_flask", name="Speed", description="Spirits of Substantial Speed", attribute="addPoints", value1="Speed", value2=1},
    cat_persuasion = {icon="cat_amulet", name="Precious Amulet of Feline Persuasion", attribute="catPersuasion"},
    testGreen={icon="green_staff"},
    testSnake={icon="snake_staff"},
}

function InventoryEntry:init(item)
    self.item = item
    self.name = item.name
    self.count = 0
end

Second mistake? Maybe cleaning the code would be better?

OK, so they have all these table elements. We’ll want to use those to set up the loot. This may take a little trial and error.

function Loot:fromInventoryItem(tile, item)
    local kind = item:kind()
    local icon = item:icon()
    return Loot(tile,kind,icon, 4,4)
end

I’m feeling nervous. Didn’t heed the feeling.

I’m out on a tightrope here. This won’t work until I get to the other end. And now I’ve just added a new parameter to Loot’s init, the icon. So I need to provide for that inside the init, and I need to change all the calls to construct Loots:

function Loot:init(tile, kind, icon, min, max)
    self.kind = kind
    self.icon = icon or self:getIcon(self.kind)
    self.min = min
    self.max = max
    self.desc = LootDescriptions[self.kind] or self.kind
    self.message = "I am a valuable "..self.desc
    if tile then tile:moveObject(self) end
end

Deferring necessary work?

We’ll want to sort out the table. Probably we’ll eliminate it and use the InventoryItems instead. Now for creators:

function DungeonBuilder:placeLoots(n)
    for i =  1, n or 1 do
        local tab = RandomLootInfo[math.random(1,#RandomLootInfo)]
        local tile = self:randomRoomTileAvoidingRoomNumber(self.playerRoomNumber)
        Loot(tile, tab[1], nil, tab[2], tab[3])
    end
end

There’s only this one and the one in LearningLevel, which I also changed. Let’s test this and see what happens. To help out, I’ll add more chests temporarily.

Oops, I forgot to put those methods on InventoryItem.

function InventoryItem:icon()
    return self.icon
end

function InventoryItem:kind()
    return self.description
end

Again I know I’m in trouble but press on.

I have low hopes for this. But with luck we’ll have most of the wires connected up right. In my mind I’m considering this a spike that I can toss if I need to.

The tests tell me that icon is a string. I’m going to ignore that and see what happens in the game.

Inventory:356: attempt to concatenate a function value (field 'description')
stack traceback:
	Inventory:356: in method 'informObjectAdded'
	Inventory:303: in method 'addToInventory'
	Decor:144: in method 'giveItem'
	Decor:101: in method 'actionWith'
	Player:276: in local 'action'
	TileArbiter:29: in method 'moveTo'
	Tile:129: in method 'attemptedEntranceBy'
	Tile:442: in function <Tile:440>
	(...tail calls...)
	Player:216: in method 'moveBy'
	Player:152: in method 'executeKey'
	Player:206: in method 'keyPress'
	GameRunner:243: in method 'keyPress'
	Main:107: in function 'keyboard'

Good idea here: revert.

OK. I could try to track that down, but it seems that my rope can’t span the gap. Revert, come up with new plan.

What Have We Learned

So that experiment has taught us some stuff, including:

  • Loot cannot contain al possible InventoryItems.
  • The notion of icon, description, name, and such do not seem to be consistent between Loot and InventoryItem.
  • There are only a few creators of Loot, so we have some flexibility.
  • There seems to be no decent way to define the value of a power-up (health, strength, etc).

It seems to me that the right thing to do is for an InventoryItem to know how to wrap itself in a Loot, rather than for Loot to know how to unwrap an InventoryItem. Furthermore, it seems that Loot should be a nearly trivial container for an InventoryItem, deferring matters like display to it.

Another bit of nastiness is this:

function DungeonBuilder:placeLoots(n)
    for i =  1, n or 1 do
        local tab = RandomLootInfo[math.random(1,#RandomLootInfo)]
        local tile = self:randomRoomTileAvoidingRoomNumber(self.playerRoomNumber)
        Loot(tile, tab[1], tab[2], tab[3])
    end
end

What even are those tab values? From Loot, we know that they are kind, min, and max. So there’s where the values come from. What’s RandomLootInfo?

local RandomLootInfo = {
    {"strength", 4,9},
    {"health", 4,10},
    {"speed", 2,5 },
    {"pathfinder", 0,0},
    {"curePoison", 0,0}
}

Those strings are then looked up in Loot class:

local LootIcons = {strength="blue_pack", health="red_vial", speed="green_flask",
pathfinder="blue_jar", curePoison="red_vase"}

local LootDescriptions = {strength="Pack of Steroidal Strength Powder", health="Potent Potion of Health", speed="Potion of Monstrous Speed" }

And then passed later to InventoryItem to create the gift:

function Loot:actionWith(aPlayer)
    local item
    self:currentTile():removeContents(self)
    item = self:createInventoryItem(aPlayer)
    item:addToInventory()
end

function Loot:createInventoryItem(aPlayer)
    return InventoryItem(self.kind, math.random(self.min,self.max))
end

I still think that what I want to do is to have Loot accept just a Tile and an InventoryItem. Everything else needs to come from the item.

What do they know now?

function InventoryItem:init(aString, aValue2)
    local entry = InventoryTable:getTable(aString)
    self.icon = entry.icon
    self.name = entry.name or self.icon
    self.description = entry.description or self.name
    self.used = entry.used or nil
    self.attribute = entry.attribute or nil
    self.value1 = entry.value or entry.value1 or nil
    self.value2 = aValue2 or entry.value2 or nil
end

This is weird, so many ors. This must be matching up things that don’t all look similar. What’s InventoryTable:getTable?

function InventoryTable:getTable(name)
    return ItemTable[name] or {}
end

And we saw the ItemTable before:

local ItemTable = {
    pathfinder={ icon="blue_jar", name="Magic Jar", attribute="spawnPathfinder", description="Magic Jar to create a Pathfinding Cloud Creature" },
    rock={ icon="rock", name="Rock", attribute="dullSharpness", description="Mysterious Rock of Dullness", used="You have mysteriously dulled all the sharp objects near by." },
    curePoison={ icon="red_vase", name="Poison Antidote", attribute="curePoison" },
    health={icon="red_vial", name="Health", description="Potent Potion of Health", attribute="addPoints", value1="Health", value2=1},
    strength={icon="blue_pack", name="Strength", description="Pack of Steroidal Strength Powder", attribute="addPoints", value1="Strength", value2=1},
    speed={icon="green_flask", name="Speed", description="Spirits of Substantial Speed", attribute="addPoints", value1="Speed", value2=1},
    cat_persuasion = {icon="cat_amulet", name="Precious Amulet of Feline Persuasion", attribute="catPersuasion"},
    testGreen={icon="green_staff"},
    testSnake={icon="snake_staff"},
}

I feel the need to format this table. That’s generally a sign that things are too weird, but for now I need it. What we have here, in any case, is a table describing anything that can be an InventoryItem.

Let’s format to make it easier to spot differences.

local ItemTable = {
    pathfinder={ 
        icon="blue_jar", 
        name="Magic Jar", 
        attribute="spawnPathfinder", 
        description="Magic Jar to create a Pathfinding Cloud Creature" },
    rock={ 
        icon="rock", 
        name="Rock", 
        attribute="dullSharpness", 
        description="Mysterious Rock of Dullness", 
        used="You have mysteriously dulled all the sharp objects near by." },
    curePoison={ 
        icon="red_vase", 
        name="Poison Antidote", 
        attribute="curePoison" },
    health={
        icon="red_vial", 
        name="Health", 
        description="Potent Potion of Health", 
        attribute="addPoints", 
        value1="Health", 
        value2=1},
    strength={
        icon="blue_pack", 
        name="Strength", 
        description="Pack of Steroidal Strength Powder", 
        attribute="addPoints", 
        value1="Strength", 
        value2=1},
    speed={
        icon="green_flask", 
        name="Speed", 
        description="Spirits of Substantial Speed", 
        attribute="addPoints", 
        value1="Speed", 
        value2=1},
    cat_persuasion = {
        icon="cat_amulet", 
        name="Precious Amulet of Feline Persuasion", 
        attribute="catPersuasion"},
    testGreen={icon="green_staff"},
    testSnake={icon="snake_staff"},
}

Everyone has an icon and a name (except the test items). All but one has a description. I’ll add descriptions if missing. And change the order for consistency: icon, name, description, attribute, values.

local ItemTable = {
    pathfinder={ 
        icon="blue_jar", 
        name="Magic Jar", 
        attribute="spawnPathfinder", 
        description="Magic Jar to create a Pathfinding Cloud Creature" },
    rock={ 
        icon="rock", 
        name="Rock", 
        description="Mysterious Rock of Dullness", 
        attribute="dullSharpness", 
        used="You have mysteriously dulled all the sharp objects near by." },
    curePoison={ 
        icon="red_vase", 
        name="Poison Antidote", 
        description="Red Vase containing a cure for poison.",
        attribute="curePoison" },
    health={
        icon="red_vial", 
        name="Health", 
        description="Potent Potion of Health", 
        attribute="addPoints", 
        value1="Health", 
        value2=1},
    strength={
        icon="blue_pack", 
        name="Strength", 
        description="Pack of Steroidal Strength Powder", 
        attribute="addPoints", 
        value1="Strength", 
        value2=1},
    speed={
        icon="green_flask", 
        name="Speed", 
        description="Spirits of Substantial Speed", 
        attribute="addPoints", 
        value1="Speed", 
        value2=1},
    cat_persuasion = {
        icon="cat_amulet", 
        name="Precious Amulet of Feline Persuasion", 
        description="Precious Amulet of Feline Persuasion", 
        attribute="catPersuasion"},
    testGreen={icon="green_staff"},
    testSnake={icon="snake_staff"},
}

I’m not sure why we have a name and a description. For now I’m just going with it. Now let’s improve the creation method:

function InventoryItem:init(aString, aValue2)
    local entry = InventoryTable:getTable(aString)
    self.icon = entry.icon
    self.name = entry.name or error("no name")
    self.description = entry.description or error("no description")
    self.used = entry.used or nil
    self.attribute = entry.attribute or nil
    self.value1 = entry.value or entry.value1 or nil
    self.value2 = aValue2 or entry.value2 or nil
end

I expect this to work just fine. Test. Doesn’t. I clearly need to add name and description to the test items. Foolish of me.

Tests run with those added, but I get a “no name” error:

Inventory:325: no name
stack traceback:
	[C]: in function 'error'
	Inventory:325: in field 'init'
	... false
    end

    setmetatable(c, mt)
    return c
end:24: in global 'InventoryItem'
	DungeonBuilder:272: in method 'placeDecor'
	DungeonBuilder:202: in method 'customizeContents'
	DungeonBuilder:55: in method 'buildLevel'
	GameRunner:77: in method 'createLevel'
	Main:66: in function 'setup'

That’s this:

function DungeonBuilder:placeDecor(n)
    local sourceItems = {
        InventoryItem("cat_persuasion"),
        --
        InventoryItem("curePoison"),
        InventoryItem("pathfinder"),
        InventoryItem("rock"),
        --
        InventoryItem("nothing"),
        InventoryItem("nothing"),
        InventoryItem("nothing")--]]
    }
    local items = {}
    for i = 1,n or 10 do
        table.insert(items, sourceItems[1 + i%#sourceItems])
    end
    Decor:createRequiredItemsInEmptyTiles(items,self)
end

We were relying on a weird fact, which is that InventoryItems when added can be looked up just using their name. This may change my plans somewhat.

I’d like to revert, but I rather like the reformatting that I did. Let’s see if we can put the creation method back the way it was and proceed more judiciously.

More Judiciously?

OK, we’re good. What we had in mind was to change Chest to create a Loot containing its InventoryItem. To make that work, we want to create the Loot to get everything it needs from the item.

At least this time I know I’m experimenting.

This is another experiment. I want to commit that cleaned up table first: reformat and normalize Inventory ItemTable.

I think this time I want the Item to know how to create a Loot container. Therefore this:

function Chest:open()
    if self:isOpen() then return end
    self.opened = true
    self.pic = self.openPic
    self.item:addToInventory()
end

Becomes this:

function Chest:open()
    if self:isOpen() then return end
    self.opened = true
    self.pic = self.openPic
    item:createLoot(self:currentTile())
end

But Loot has all that convenience junk in it:

Loot = class(DungeonObject)

local LootIcons = {strength="blue_pack", health="red_vial", speed="green_flask",
pathfinder="blue_jar", curePoison="red_vase"}

local LootDescriptions = {strength="Pack of Steroidal Strength Powder", health="Potent Potion of Health", speed="Potion of Monstrous Speed" }

function Loot:init(tile, kind, min, max)
    self.kind = kind
    self.icon = self:getIcon(self.kind)
    self.min = min
    self.max = max
    self.desc = LootDescriptions[self.kind] or self.kind
    self.message = "I am a valuable "..self.desc
    if tile then tile:moveObject(self) end
end

Fool knows he’s in trouble but not what to do.

I’m having trouble picking the thread to tug on to sort this out. I think the fundamental mistake is that the Loot is referring to its own table of information about what’s in it.

Let’s have the real creation method expect all the right stuff, and then deal with the “convenience” with a factory method.

Bad guess, Ron

So, and this will surely get me in trouble but I might be able to wander back out …

function Loot:init(item, tile)
    self.item = item
    if tile then tile:moveObject(self) end
end

function Loot:getIcon(kind)
    return self.item:icon()
end

function Loot:query()
    Bus:informDotted(self.item:message())
end

function Loot:setMessage(message)
    -- to be removed
end

function Loot:actionWith(aPlayer)
    local item
    self:currentTile():removeContents(self)
    item:addToInventory()
end
--[[
function Loot:createInventoryItem(aPlayer)
    return InventoryItem(self.kind, math.random(self.min,self.max))
end
--]]

function Loot:draw(tiny, center)
    sprite(Sprites:sprite(self:getIcon()),center.x,center.y+10)
end

I expect big trouble here. Test. Remember that I’ve not changed any calls to Loot, nor finished the one I was working on:

function InventoryItem:createLoot(aTile)
    Loot(aTile,self)
end

Now the other calls to Loot( …

...
    self:createRoomsFromXYWHA(t, self.RUNNER)
    local r2 = self:getRooms()[2]
    local lootTile = r2:tileAt(2,2)
    local item = InventoryItem:named("health",5,5)
    local loot = Loot(lootTile, item)
    loot:setMessage("This is a Health Power-up of 5 points.\nStep onto it to add it to your inventory.")
...

That’s in createLearningLevel, so it doesn’t have to work. And that setMessage method has been removed. Then there’s these:

function DungeonBuilder:placeLoots(n)
    for i =  1, n or 1 do
        local tab = RandomLootInfo[math.random(1,#RandomLootInfo)]
        local tile = self:randomRoomTileAvoidingRoomNumber(self.playerRoomNumber)
        Loot(tile, tab[1], tab[2], tab[3])
    end
end

That’s coming from here:

function DungeonBuilder:placeDecor(n)
    local sourceItems = {
        InventoryItem("cat_persuasion"),
        --
        InventoryItem("curePoison"),
        InventoryItem("pathfinder"),
        InventoryItem("rock"),
        --
        InventoryItem("nothing"),
        InventoryItem("nothing"),
        InventoryItem("nothing")--]]
    }
    local items = {}
    for i = 1,n or 10 do
        table.insert(items, sourceItems[1 + i%#sourceItems])
    end
    Decor:createRequiredItemsInEmptyTiles(items,self)
end

Let’s define a new method named, on InventoryItem:

function DungeonBuilder:placeDecor(n)
    local sourceItems = {
        InventoryItem:named("cat_persuasion"),
        --
        InventoryItem:named("curePoison"),
        InventoryItem:named("pathfinder"),
        InventoryItem:named("rock"),
        --
        InventoryItem:named("nothing"),
        InventoryItem:named("nothing"),
        InventoryItem:named("nothing")--]]
    }

And … no, I think that’s not needed. I’ll add it anyway:

function InventoryItem:named(aString)
    return InventoryItem(aString, nil)
end

Hope still not a strategy …

Honestly I am lost. But I think I might be close to home. Test.

Still getting this:

Loot:13: attempt to call a nil value (method 'moveObject')
stack traceback:
	Loot:13: in field 'init'
	... false
    end

    setmetatable(c, mt)
    return c
end:24: in global 'Loot'
	DungeonBuilder:295: in method 'placeLoots'
	DungeonBuilder:202: in method 'customizeContents'
	DungeonBuilder:55: in method 'buildLevel'
	GameRunner:77: in method 'createLevel'
	Main:66: in function 'setup'

Need to change the sequence there?

function DungeonBuilder:placeLoots(n)
    for i =  1, n or 1 do
        local item = RandomLootInfo[math.random(1,#RandomLootInfo)]
        local tile = self:randomRoomTileAvoidingRoomNumber(self.playerRoomNumber)
        Loot(tile, item)
    end
end

Why not stop? You know you’re confused.

Maybe. I am really uncertain and fumbling. But we haven’t made much of a mess and it feels so close …

Wait, did I reverse the order in Loot:init? Sure did.

function DungeonBuilder:placeLoots(n)
    for i =  1, n or 1 do
        local item = RandomLootInfo[math.random(1,#RandomLootInfo)]
        local tile = self:randomRoomTileAvoidingRoomNumber(self.playerRoomNumber)
        Loot(item,tile)
    end
end

Test. Some tests fail. Ignoring those. When I go to open a Chest:

Chest:32: attempt to index a nil value (global 'item')
stack traceback:
	Chest:32: in method 'open'
	Player:268: in local 'action'
	TileArbiter:29: in method 'moveTo'
	Tile:129: in method 'attemptedEntranceBy'
	Tile:442: in function <Tile:440>
	(...tail calls...)
	Player:216: in method 'moveBy'
	Player:152: in method 'executeKey'
	Player:206: in method 'keyPress'
	GameRunner:243: in method 'keyPress'
	Main:107: in function 'keyboard'

Ha. Forgot a self?

function Chest:open()
    if self:isOpen() then return end
    self.opened = true
    self.pic = self.openPic
    self.item:createLoot(self:currentTile())
end

OK try again … get this error just walking near a chest:

Loot:17: attempt to call a nil value (method 'icon')
stack traceback:
	Loot:17: in method 'getIcon'
	Loot:40: in method 'draw'
	DungeonContentsCollection:113: in method 'draw'
	GameRunner:164: in method 'drawMapContents'
	GameRunner:130: in method 'draw'
	Main:102: in function 'draw'

Wrong guess suggests I’m lost. Press on regardless.

I bet that’s a nothing. Let me remove those from the table for now. Error still happens. Better read the code.

function Loot:getIcon(kind)
    return self.item:icon()
end

Maybe I forgot the method. I wrote it once but perhaps reverted it out?

Grr, I can’t call it icon because i can’t override a member name. So:

function InventoryItem:getIcon()
    return self.icon
end

And …

function Loot:getIcon(kind)
    return self.item:getIcon()
end

And test. And same error. This is terrible. I can’t publish this. What’s going on here?

Loot:17: attempt to call a nil value (method 'getIcon')
stack traceback:
	Loot:17: in method 'getIcon'
	Loot:40: in method 'draw'
	DungeonContentsCollection:113: in method 'draw'
	GameRunner:164: in method 'drawMapContents'
	GameRunner:130: in method 'draw'
	Main:102: in function 'draw'

I must have a loot with no item?

Look at this! Stop!

Instrument. I’m going to bull my way through, then throw all this away. Unless it works.

I find that I didn’t reverse the parms here:

function InventoryItem:createLoot(aTile)
    Loot(self,aTile)
end

Test some more. A chest opens and displays a blue back, which is unfortunately the one I chose if things go badly. And then I get this error when I try to pick it up:

Loot:35: attempt to index a nil value (local 'item')
stack traceback:
	Loot:35: in method 'actionWith'
	Player:294: in local 'action'
	TileArbiter:29: in method 'moveTo'
	Tile:129: in method 'attemptedEntranceBy'
	Tile:442: in function <Tile:440>
	(...tail calls...)
	Player:216: in method 'moveBy'
	Player:152: in method 'executeKey'
	Player:206: in method 'keyPress'
	GameRunner:243: in method 'keyPress'
	Main:107: in function 'keyboard'

I chuckle:

function Loot:actionWith(aPlayer)
    local item
    self:currentTile():removeContents(self)
    item:addToInventory()
end

I never finished that method. Now we actually have an item, I think, so:

function Loot:actionWith(aPlayer)
    self:currentTile():removeContents(self)
    self.item:addToInventory()
end

I’m beginning to see light at the end of the tunnel. But I don’t see how I can recover this article. Never mind, we’ll fix it in post.

It starts to work. But still not good.

Test. I have good news and bad news. The Chests now seem to work, although the Cat Persuasion Amulet is hard to see. But I received health, strength, and cat persuasion from Chests.

The bad news is that my missing icon message is coming out a lot. I guess that makes sense, in that the game tries to display things a lot.

The message isn’t informative enough:

no getIcon in table: 0x280b0ee00

With various table names. First thing I want to know is what this table is. It must be either an InventoryItem or a Tile, mustn’t it? Let’s add some __tostring up in this baby.

InventoryItem has a __tostring. Surely Tile does?

Yes, it has. So what is in there? Beef up the message. Printing the table tells me the offenders are all tables indexed by 1,2,3 and the first element is either speed or catPersuasion.

That’s that tab[1], tab[2], tab[3] stuff isn’t it? But I don’t find the string tab[1] any more. Better halt it.

I get a speed,2,5 and halt here:

Loot:22: whazzup
stack traceback:
	[C]: in function 'error'
	Loot:22: in method 'getIcon'
	Loot:47: in method 'draw'
	DungeonContentsCollection:113: in method 'draw'
	GameRunner:164: in method 'drawMapContents'
	GameRunner:130: in method 'draw'
	Main:102: in function 'draw'

The location doesn’t help me. Are these pure loots lying around? I put an error in the Loot init if there is an item[1] and (finally) get this error:

Loot:13: Not an InventoryItem
stack traceback:
	[C]: in function 'error'
	Loot:13: in field 'init'
	... false
    end

    setmetatable(c, mt)
    return c
end:24: in global 'Loot'
	DungeonBuilder:295: in method 'placeLoots'
	DungeonBuilder:202: in method 'customizeContents'
	DungeonBuilder:55: in method 'buildLevel'
	GameRunner:77: in method 'createLevel'
	Main:66: in function 'setup'

OK, placeLoots is bad.

function DungeonBuilder:placeLoots(n)
    for i =  1, n or 1 do
        local item = RandomLootInfo[math.random(1,#RandomLootInfo)]
        local tile = self:randomRoomTileAvoidingRoomNumber(self.playerRoomNumber)
        Loot(item,tile)
    end
end

Right. Forgot to turn those into InventoryItems.

Post Note: This is clearly something that we’d have done well to resolve early on.

local RandomLootInfo = {
    {"strength", 4,9},
    {"health", 4,10},
    {"speed", 2,5 },
    {"pathfinder", 0,0},
    {"curePoison", 0,0}
}

Tell you what, bunkie, I’m tired and getting more tired. I can use my named method here though:

function DungeonBuilder:placeLoots(n)
    for i =  1, n or 1 do
        local info = RandomLootInfo[math.random(1,#RandomLootInfo)]
        local item = InventoryItem:named(info[1])
        local tile = self:randomRoomTileAvoidingRoomNumber(self.playerRoomNumber)
        Loot(item,tile)
    end
end

I think this should get things close to working.

I have good news. Everything is working, with an exception that does not surprise me. When I find a health powerup, it always is +1.

We’re going to have to come up with a scheme for setting the values on the powerups, and I’m too tired to do that much thinking this morning.

I commit but fortunately change my mind below.

I can certainly commit this code and with permission, ship it, with the caveat that powerups only offer +1. We can argue about shipping. Everything else is solid, and I’m going to commit: Chests now show their items again. Loots contain actual InventoryItems. System defers to InventoryItem to display them. All powerups provide only +1 (to be improved).

Let’s sum up and get the hell out of here.

Summary

I think we finally have a nearly decent rope bridge across the chasm here, but it certainly didn’t go well or smoothly.

Looking back, it seems that there were too many small hacks for convenience, things that really needed to be improved but that, for whatever reason, I left in and moved on.

You know what? I’m going to belay this whole thing and undo that last commit. I’ll do it again next time, and do it better. Commit undone. Revert. No, I didn’t even stash it. Nothing. Gone.

Tomorrow (or Monday) is another day. Maybe even this afternoon. But this was not a good session and while the code is working, I can’t say that it’s good. It might have been, but odds are against it.

I’m going back and removing as much of the cruft from the article as I can but I’m going to publish it. Consider this one of those “Don’t be like Ron” articles.

The lesson I’m taking home is to take even smaller steps, and to clean up some of these limited convenience tables. I might make some of the parameters of the InventoryItems fixed for now, figure out how to vary then as we move forward on the big story.

I’m not sure what the small steps are. This path was pretty close, but I think I can do better.

Would you have reverted? Think about why, or why not? And next time we’ll see if what I come up with is better or follows a smoother path.

Ow. Bear bites hurt.