Up to Main Index                          Up to Journal for February, 2018

                  JOURNAL FOR WEDNESDAY 28TH FEBRUARY, 2018
______________________________________________________________________________

SUBJECT: Save me!
   DATE: Wed 28 Feb 19:54:18 GMT 2018

I spent Thursday[1] evening last week modifying the Thing.Marshal method so
that it could recurse into inventories. I also put together a very simple SAVE
command to write out the currently logged in player and their inventory. At
the moment the SAVE command always writes to DATA_DIR/players/save.wrj so that
I don’t accidentally blow away any actual player files while testing.

How simple was the simple SAVE command?


  package cmd

  import (
    "os"
    "path/filepath"

    "code.wolfmud.org/WolfMUD.git/attr"
    "code.wolfmud.org/WolfMUD.git/config"
    "code.wolfmud.org/WolfMUD.git/recordjar"
  )

  // Syntax: SAVE
  func init() {
    addHandler(save{}, "SAVE")
  }

  type save cmd

  func (save) process(s *state) {

    j := s.actor.(*attr.Thing).Marshal(recordjar.Jar{})
    n := filepath.Join(config.Server.DataDir, "players", "save.tmp")
    f, _ := os.Create(n)
    j.Write(f, "DESCRIPTION")
    f.Close()

    s.msg.Actor.SendInfo("You have been saved.")
    s.ok = true
  }


That’s the whole command for testing — yes, sans error handling. The point of
the exercise was to see how complete and accurate the saved data was. Below
are the results. The left hand side shows the saved player after picking up
the iron bound chest and green ball in the tavern. The right hand side shows
the original definitions from the zinara.wrj zone file. Down the middle a ‘|’
indicates lines that are different and ‘>’ indicates an additional line.


  save.wrj                             zinara.wrj
  --------                             ----------

     Gender: MALE                     |
  Inventory: #UID-3B #UID-3D          |
       Name: Diddymus                 |
      Alias: DIDDYMUS                 |
        Ref: #UID-6J                  |
  %%                                     %%
        Ref: #UID-3B                  |        Ref: O4
       Name: an iron bound chest              Name: an iron bound chest
      Reset: AFTER→1M JITTER→1M SPAWN |      Reset: AFTER→1m JITTER→1m
      Alias: CHEST                    |    Aliases: CHEST
    Cleanup: AFTER→10M JITTER→5M           Cleanup: AFTER→10m JITTER→5m
                                      |   Location: L1
  Inventory: #UID-3A                  |  Inventory: O4A

  This is a very stout wooden chest a    This is a very stout wooden chest a
  bands bind it.                         bands bind it.
  %%                                     %%
        Ref: #UID-3A                  |        Ref: O4A
       Name: a small leather pouch            Name: a small leather pouch
      Reset: AFTER→1M JITTER→1M SPAWN |      Reset: AFTER→1m JITTER→1m
      Alias: POUCH                    |    Aliases: POUCH
    Cleanup: AFTER→10M JITTER→10M          Cleanup: AFTER→10m JITTER→10m
  Inventory: #UID-39                  |  Inventory:

  This is a small pouch made of soft     This is a small pouch made of soft
  %%                                     %%
      Ref: #UID-39                    |       Ref: O3
     Name: a small red ball                  Name: a small red ball
    Reset: AFTER→0S JITTER→2M SPAWN→T |     Reset: AFTER→0s JITTER→2m SPAWN
    Alias: BALL                       |   Aliases: BALL
  Cleanup: AFTER→1S JITTER→0S         |   Cleanup: AFTER→1s
                                      >  Location: O4A

  This is a small, red, rubber ball.     This is a small, red, rubber ball.
  %%                                     %%
        Ref: #UID-3D                  |        Ref: O2
       Name: a small green ball               Name: a small green ball
      Reset: SPAWN→FALSE AFTER→0S JIT |      Reset: AFTER→0s JITTER→0s
      Alias: BALL                     |    Aliases: BALL
    Cleanup: AFTER→0S JITTER→0S            Cleanup: AFTER→0s JITTER→0s
    Onreset: There is a bright flash       OnReset: There is a bright flash
                                      >   Location: L1
  Oncleanup: There is a bright flash     OnCleanup: There is a bright flash

  This is a small, green, rubber ball    This is a small, green, rubber ball
  %%                                      %%


In order to compare the two I’ve ordered the field names within each record
consistently. The reference identifiers are the most obvious difference. This
is expected as the ids have to be unique within the file, so we just use the
already available system unique id. The saved version will favour the use of
Inventory over Location. Inventory/Location are interchangeable, depending on
whether you want to specify ‘that goes here’ (Inventory) or ‘this goes there’
(Location). The saved version also looks at the number of aliases and uses
Alias or Aliases accordingly.

When players log out their character and its current inventory are saved and
removed from the world. When they log back in again, the character and its
inventory are loaded and added back into the world. However, not all items can
be kept by a player and removed from the world. This is because some items are
unique and needed by other players. An example would be a key for a door. If
the key was removed from the world, then no other players would be able to
unlock the door and open it.

The only items a player can keep are those that are created or duplicated in
the world. Currently this means any item that is spawnable. Later on it will
also mean items that are bought or crafted. Bought items will be cloned from a
shop’s inventory. Crafted items will be created from a template item.

Hypothetically, in the case of the key above, it could be possible for a
player to craft a duplicate key, which they could then keep.

Thinking through the above, players can keep any item that can be disposed of
permanently. There is already a check for this in the JUNK command. If an item
has no origin set and no reset attribute then it is permanently disposed of
when cleaned up. When we save a player and their inventory, we simply ignore
items that are not permanently disposable.

This leads to a clean way of extracting the player from the world after they
have been saved. We JUNK the player. Using the JUNK command items will be
cleaned up and either disposed of or reset as appropriate. Plus the code is
already written :)

While experimenting with using the JUNK command on a player I noticed a weird
bug in the message buffer handling:


  >quit
  You junk an iron bound chest.
  You leave this world behind.
  You junk a small green ball.
  You leave this world behind.

    Main Menu
    ---------

    1. Enter game
    0. Quit

  Select an option:
  >0

  Bye bye...

  Connection closed by foreign host.


Notice that “You leave this world behind.” appears twice when it should only
appear once. This only seems to happen if a player quits when there is nothing
else at the same location. Not sure why, will investigate.

Once I have all of the above sorted out, I just need to work on reversing the
process and loading the player and their inventory back into the world. For
that I need to delve into the zone loader and start refactoring it into a
generic .wrj file loader.

On a side note, I’ve switched to using Go 1.10 for development with no issues.

--
Diddymus

  [1] Yes, this post has been delayed quite a bit again…


  Up to Main Index                          Up to Journal for February, 2018