Up to Main Index                             Up to Journal for March, 2018

                    JOURNAL FOR SATURDAY 24TH MARCH, 2018
______________________________________________________________________________

SUBJECT: Out of the rabbit hole
   DATE: Sat 24 Mar 19:27:41 GMT 2018

I really don’t like the phrase ‘permanently disposable items’, it’s a bit of a
mouthful and a pain to keep typing. I was thinking of calling ‘permanently
disposable items’ collectable, as these are items players can collect and
keep. Non-collectable items cannot be kept by players. Let’s try it for this
journal entry and see how it goes…

At long last I have found my way out of a deep rabbit hole. For the last few
weeks I’ve been working on saving players. I think most players would agree
this is quite an important feature to have. While working on said feature I’ve
tried many different approaches to modifying the existing code. For example,
changing the Thing.Marshal method to save recursively, changing Inventory to
only Marshal references for collectable items, changing the Marshal interface
so that the methods can take behaviour modifying parameters.

However, all of these changes resulted in Thing.Marshal and Inventory.Marshal
becoming specialised and incapable of marshaling any Thing or any Inventory.
This in turn would have meant no saving and persisting of the world using
existing code. Actually I think ‘broken’ would have been better description
for the changes. After a while I stopped banging my head against the wall, and
deleted all of my ill conceived changes.

Sometimes it’s better to throw out changes and spending some time just sitting
and thinking about a problem. What was I actually trying to achieve here? I
wanted to marshal a Thing, if the Thing had an Inventory I wanted to marshal
those Thing as well — but only if they were collectable. I could actually do
that in the save command quite easily without touching any other code:


  func (sa save) inventory(jar *recordjar.Jar, t has.Thing) {
    *jar = append(*jar, t.(*attr.Thing).Marshal())

    if i := attr.FindInventory(t); i.Found() {
      for _, t := range i.Contents() {
        if t.Collectable() {
          sa.inventory(jar, t)
        }
      }
    }
  }


Oops! Little white lie there, I touched Thing to add a Collectable method.
This wraps a call to FindLocate(t).Origin(), but means that the definition of
what is considered collectable can be easily changed.

The inventory method takes the Jar we want to marshal into and the Thing we
want to marshal into it. In doing so we end up with incorrect Inventory fields
in the Jar because Inventory marshals all of the references it knows about.

For example, we might end up with this snippet:



        Ref: #UID-6O
       Name: a small sack
      Alias: SACK
    Cleanup: AFTER→10M JITTER→5M
  Inventory: #UID-6P #UID-6Q

  This is a small sack, handy for carrying things in.
  %%
      Ref: #UID-6P
     Name: a small red ball
    Alias: BALL
  Cleanup: AFTER→1S JITTER→0S

  This is a small, red, rubber ball.
  %%


Here we have a sack that contains two items #UID-6P and #UID-6Q. Reference
#UID-6P is the small red ball. We don’t know what #UID-6Q was because it
wasn’t collectable, and hence not saved. This was why I originally tried
modifying the Inventory.Marshal method — to only write out references for
collectable items.

However, what we have now is a Jar with records that can be easily processed.
Why not fix the Jar by rewriting the inventory fields so that they only
contain references found in the Jar?


  func (sa save) fixInventory(jar *recordjar.Jar) {

    // Extract all "ref" fields from the jar
    refs := make(map[string]struct{})
    for _, rec := range *jar {
      refs[string(rec["ref"])] = struct{}{}
    }

    // Find all of the "inventory" fields in the jar and rewrite them to only
    // contain references found in the jar
    for _, rec := range *jar {
      if i, ok := rec["inventory"]; ok {
        newRefs := []string{}
        for _, ref := range decode.KeywordList(i) {
          if _, ok := refs[ref]; ok {
            newRefs = append(newRefs, ref)
          }
        }
        rec["inventory"] = encode.KeywordList(newRefs)
      }
    }
  }


That took a lot longer to debug than it should have. Mainly due to the field
name map keys being a mess of all upper, all lower and mixed case. As a result
the recordjar.Write method now does a better job of normalising the field
names.

The code for the SAVE command and fixes to the recordjar package are now on
the public dev branch. The SAVE command will only write to the file ‘save.wrj’
and not to real player files yet. I’m still working on being able to load
player files saved with the SAVE command. The save files are still missing the
account information record and the loader doesn’t load any saved items yet.

Non-collectable containers are still an issue :( A non-collectable container
will not be saved, as expected, but ALL of its content, whether collectable or
not, will not be saved either. This results in ALL items in non-collectable
containers effectively being ‘lost’ when a player is saved. For now a simple
solution is to not create non-collectable containers while I try to come up
with a solution.

One possible solution would be to take the collectable items out of the
non-collectable container and put them into the parent container. If the
parent container was full the item would need to be pushed further up the
container hierarchy. This could eventually push the item into the player’s
main inventory. If the player’s main inventory was full the item would push
all the way up to the current location the player is in — resulting in the
item being dropped. In the end the logic works out neatly, although the
player’s inventory may be left in a mess.

To help with testing I added two new items to the tavern. A bag and a sack,
both are collectable. They can be used to experiment with the SAVE command and
its interaction with containers in containers and items in containers — which
maybe in other containers:


        Ref: O9
       Name: a small sack
    Aliases: SACK
      Reset: AFTER→1m JITTER→1m SPAWN
    Cleanup: AFTER→10m JITTER→5m
   Location: L1
  Inventory:

  This is a small sack, handy for carrying things in.
  %%
        Ref: O10
       Name: a small bag
    Aliases: BAG
      Reset: AFTER→1m JITTER→1m SPAWN
    Cleanup: AFTER→10m JITTER→5m
   Location: L1
  Inventory:

  This is a small bag, handy for carrying things in.
  %%


The bag and sack have been included in the dev branch update.

For collectable items the small red ball, found in the pouch that is in the
chest, already exists. There are also mushrooms in the undergrowth in the
forest to the south.

This was only supposed to be a quick entry and I was going to spend the
afternoon coding…

--
Diddymus


  Up to Main Index                             Up to Journal for March, 2018