[Home] [Downloads] [Search] [Help/forum]


Register forum user name Search FAQ

Gammon Forum

[Folder]  Entire forum
-> [Folder]  SMAUG
. -> [Folder]  SMAUG coding
. . -> [Subject]  Lua utility to analyze SmaugFuss area files

Lua utility to analyze SmaugFuss area files

It is now over 60 days since the last post. This thread is closed.     [Refresh] Refresh page


Posted by Nick Gammon   Australia  (22,985 posts)  [Biography] bio   Forum Administrator
Date Sun 02 Mar 2008 08:54 PM (UTC)

Amended on Sun 02 Mar 2008 08:56 PM (UTC) by Nick Gammon

Message
For the fun of it, I wrote a small Lua utility to analyze the new format (keyed) SMAUG area files.

What I mean by "keyed" is that entries now look like this:


#MOBILE
Vnum       10300
Keywords   wolf arena~
Short      the dread wolf~
Long       A dread wolf is hunting here
~
Race       human~
Class      warrior~
Position   standing~
DefPos     standing~
Gender     neuter~
Actflags   npc stayarea prototype~
Stats1     0 4 0 35 500 0
Stats2     44 0 1
Stats3     0 0 0
Stats4     0 0 0 0 0
Attribs    13 13 13 13 13 13 13
Saves      0 0 0 0 0
Speaks     common~
Speaking   common~
Bodyparts  legs feet eye tail~
Immune     charm~
#ENDMOBILE


This lends itself somewhat to a simple utility to read the whole area file in. I ran this using the stand-alone Lua executable, however it also works from the MUSHclient "immediate" window with Lua scripting. To use it you would need to change the first line to represent where your Smaug "area" directory is on your hard disk.


local AREA_DIR = "C:\\cygwin\\home\\Owner\\smaugfuss\\area\\"
         
local hdr -- area header
local mobs  = {} -- all mobs
local objects = {} -- all objects
local rooms = {} -- all rooms
local helps = {} -- help stuff

require "tprint"
require "pairsbykeys"
   
-- extract keyed data, items in "strings" are tilde-terminated
function extract_stuff (src, strings)

 local tbl = {}
 
 -- note: we expect \n to be at each end of src 
 
 -- strings might be multi-line, and are terminated by the ~ character
 for key in string.gmatch (strings, "[%a%d]+") do
    src = string.gsub (src, "(" .. key .. ")%s+(.-)~", 
          function (k, v)
            assert (not tbl [k], "Duplicate string item: " .. k .. " " .. v)  -- should not already exist
            tbl [k] = v
            return ""
          end -- function
          )
  end -- for all strings
  
  -- everything else is single line, terminated by \n
  src = string.gsub (src, "([%a%d]+)%s+(.-)\n", 
        function (k, v)
          assert (not tbl [k], "Duplicate item: " .. k .. " " .. v)  -- should not already exist
          tbl [k] = v
          return ""
        end -- function
        )

  -- should have nothing left, if not, we have some sort of problem
  if (string.match (src, "[^%s]")) then
    tprint (tbl)
    error ("Got extra data:" .. src)
  end -- if    
  
  return tbl
end -- extract_stuff

-- area header has stuff like author, reset message
function process_area_header (ah)
  hdr = extract_stuff (ah, "Name Author ResetMsg Flags")
end -- process_area_header

-- do each mobile
function process_mobile (m)
local mudprogs = {}

  -- first pull out the mud progs sub-items
  m = string.gsub (m, "#MUDPROG(\n.-\n)#ENDPROG\n",
       function (p)
         table.insert (mudprogs, extract_stuff (p, "Progtype Arglist Comlist"))
         return ""
       end -- function
       )
       
  local mob = extract_stuff (m, 
       "Keywords Short Long Race Class Position DefPos Gender Actflags Speaks Speaking Bodyparts Immune Desc Defenses Attacks")

  if next (mudprogs) ~= nil then
    mob.mudprogs = mudprogs
  end -- have some mud progs
  
  local vnum = assert (tonumber (mob.Vnum))
  assert (not mobs [vnum], "Duplicate mob vnum: " .. vnum)  -- should not already exist
  
  mobs [vnum] = mob
  
end -- process_mobile

-- do each object
function process_object (m)
local mudprogs = {}
local extra_descs = {}
local affects = {}

  -- first pull out the mud progs sub-items
  m = string.gsub (m, "#MUDPROG(\n.-\n)#ENDPROG\n",
       function (p)
         table.insert (mudprogs, extract_stuff (p, "Progtype Arglist Comlist"))
         return "\n"
       end -- function
       )

  -- then pull out the extra description sub-items
  m = string.gsub (m, "#EXDESC(\n.-\n)#ENDEXDESC\n",
       function (p)
         table.insert (extra_descs, extract_stuff (p, "ExDescKey ExDesc"))
         return "\n"
       end -- function
       )
    
  -- and the affects of which there might be more than one
  m = string.gsub (m, "Affect%s+(.-)\n",
       function (p)
         table.insert (affects, p)
         return "\n"
       end -- function
       )
       
  local object = extract_stuff (m, 
       "Keywords Short Long Type WFlags Flags")

  if next (mudprogs) ~= nil then
    object.mudprogs = mudprogs
  end -- have some mud progs
       
  if next (extra_descs) ~= nil then
    object.extra_descs = extra_descs
  end -- have some extra descriptions

  if next (affects) ~= nil then
    object.affects = affects
  end -- have some affects
  
  local vnum = assert (tonumber (object.Vnum))
  assert (not objects [vnum], "Duplicate object vnum: " .. vnum)  -- should not already exist
  
  objects [vnum] = object
  
end -- process_object

-- do each room
function process_room (m)
local mudprogs = {}
local exits = {}
local resets = {}
local exit_descs = {}

  -- first pull out the mud progs sub-items
  m = string.gsub (m, "#MUDPROG(\n.-\n)#ENDPROG\n",
       function (p)
         table.insert (mudprogs, extract_stuff (p, "Progtype Arglist Comlist"))
         return ""
       end -- function
       )

  -- then pull out the exits
  m = string.gsub (m, "#EXIT(\n.-\n)#ENDEXIT\n",
       function (p)
         table.insert (exits, extract_stuff (p, "Direction Desc"))
         return ""
       end -- function
       )

  -- then pull out the exit descriptions
  m = string.gsub (m, "#EXDESC(\n.-\n)#ENDEXDESC\n",
       function (p)
         table.insert (exit_descs, extract_stuff (p, "ExDescKey ExDesc"))
         return ""
       end -- function
       )

  -- and the resets of which there might be more than one
  m = string.gsub (m, "Reset%s+(.-)\n",
       function (p)
         table.insert (resets, p)
         return "\n"
       end -- function
       )
       
  local room = extract_stuff (m, 
       "Name Sector Flags Desc", {})

  if next (mudprogs) ~= nil then
    room.mudprogs = mudprogs
  end -- have some mud progs
       
  if next (exits) ~= nil then
    room.exits = exits
  end -- have some exits

  if next (exit_descs) ~= nil then
    room.exit_descs = exit_descs
  end -- have some exits descriptions
  
  if next (resets) ~= nil then
    room.resets = resets
  end -- have some resets
  
  local vnum = assert (tonumber (room.Vnum))
  assert (not rooms [vnum], "Duplicate room vnum: " .. vnum)  -- should not already exist
  
  rooms [vnum] = room
  
end -- process_room

-- areas consist of area header, mobiles, objects, rooms
function process_area (areadata)
local m
 
  -- process header stuff
  for m in string.gmatch (areadata, "#AREADATA(\n.-\n)#ENDAREADATA\n") do
    process_area_header (m)
  end -- for
  
  -- now mobs
  for m in string.gmatch (areadata, "#MOBILE(\n.-\n)#ENDMOBILE\n") do
    process_mobile (m)
  end -- for

  -- now objects
  for m in string.gmatch (areadata, "#OBJECT(\n.-\n)#ENDOBJECT\n") do
    process_object (m)
  end -- for

  -- now rooms
  for m in string.gmatch (areadata, "#ROOM(\n.-\n)#ENDROOM\n") do
    process_room (m)
  end -- for
  
end -- process_area

-- handle all help stuff in help.are
function process_helps (areadata)

  string.gsub (areadata, "%s*(%d+)%s+(.-)~\n(.-)~", 
        function (level, keyword, text)
          table.insert (helps, { level = tonumber (level), keyword = keyword, text = text } )
        end -- function
        )
        
end -- process_helps

-- help function to show count of items in a table
function show_counts (tbl, desc)
local count = 0
  for k in pairs (tbl) do
    count = count + 1
  end -- for
  
  print (count, desc)
end -- show_counts

-------- MAIN PROCESSING STARTS HERE --------------------

-- read each line in area.lst file
for line in io.lines (AREA_DIR .. "area.lst") do
    
  -- end of file marker
  if line == "$" then
    break
  end -- if
  
  -- area file name
  local filename = AREA_DIR .. line
  
  -- read area in
  local f = io.open (filename, "rt")
  local area = f:read ("*a")
  f:close ()
  
  -- show name and size
  print ("Opened " .. filename .. ", got " .. #area .. " bytes")
  
  -- process entire area
  for a in string.gmatch (area, "#FUSSAREA(\n.-\n)#ENDAREA\n") do
    process_area (a)
  end -- for

  -- do helps
  for a in string.gmatch (area, "#HELPS(\n.-\n)#$\n") do
    process_helps (a)
  end -- for
  
end 

-- show how many we found
show_counts (mobs, "mobs")
show_counts (objects, "objects")
show_counts (rooms, "rooms")
show_counts (helps, "helps")



It uses the area.lst file to find which area files to process, and reads the lot in. On my PC it took 2 seconds to process all 24 area files.

Running it, I see this:


Opened C:\cygwin\home\Owner\smaugfuss\area\help.are, got 602742 bytes
Opened C:\cygwin\home\Owner\smaugfuss\area\limbo.are, got 102567 bytes
Opened C:\cygwin\home\Owner\smaugfuss\area\gods.are, got 2202 bytes
Opened C:\cygwin\home\Owner\smaugfuss\area\newacad.are, got 233672 bytes
Opened C:\cygwin\home\Owner\smaugfuss\area\newgate.are, got 66917 bytes
Opened C:\cygwin\home\Owner\smaugfuss\area\newdark.are, got 256634 bytes
Opened C:\cygwin\home\Owner\smaugfuss\area\plains.are, got 38843 bytes
Opened C:\cygwin\home\Owner\smaugfuss\area\haon.are, got 100273 bytes
Opened C:\cygwin\home\Owner\smaugfuss\area\midennir.are, got 70083 bytes
Opened C:\cygwin\home\Owner\smaugfuss\area\sewer.are, got 139946 bytes
Opened C:\cygwin\home\Owner\smaugfuss\area\redferne.are, got 16883 bytes
Opened C:\cygwin\home\Owner\smaugfuss\area\grove.are, got 32229 bytes
Opened C:\cygwin\home\Owner\smaugfuss\area\dwarven.are, got 35524 bytes
Opened C:\cygwin\home\Owner\smaugfuss\area\daycare.are, got 32186 bytes
Opened C:\cygwin\home\Owner\smaugfuss\area\grave.are, got 26812 bytes
Opened C:\cygwin\home\Owner\smaugfuss\area\chapel.are, got 57517 bytes
Opened C:\cygwin\home\Owner\smaugfuss\area\astral.are, got 113452 bytes
Opened C:\cygwin\home\Owner\smaugfuss\area\Build.are, got 43819 bytes
Opened C:\cygwin\home\Owner\smaugfuss\area\pixie.are, got 21871 bytes
Opened C:\cygwin\home\Owner\smaugfuss\area\export.are, got 101487 bytes
Opened C:\cygwin\home\Owner\smaugfuss\area\srefuge.are, got 148135 bytes
Opened C:\cygwin\home\Owner\smaugfuss\area\manor.are, got 107487 bytes
Opened C:\cygwin\home\Owner\smaugfuss\area\unholy.are, got 104256 bytes
Opened C:\cygwin\home\Owner\smaugfuss\area\gallery.are, got 134695 bytes
514 mobs
741 objects
1812 rooms
1091 helps


Each "type" of data is built into its own Lua table, so you can then analyze them (eg. all mobs into the mobs table).

With the tables built you can then start analyzing your areas. For example, by adding this to the bottom of the script:


------------- EXITS CROSS-REFERENCE ----------------------

-- look for exits cross-reference
for room_vnum, room_data in pairs (rooms) do
 
  -- if room has exits, add them to the target room
  if room_data.exits then
    for _, ex in ipairs (room_data.exits) do
      local room = tonumber (ex.ToRoom)
      if rooms [room] then  -- to room might be out of area
        rooms [room].from_room = rooms [room].from_room or {}  -- make table if first time
        rooms [room].from_room [room_vnum] = true  -- there is an exit from room_vnum to room
      else
        print ("Room ", room_vnum, "leads to non-existent room", room)
      end -- room exists
    end -- for each exit
  
  end -- some exits

end -- for each room

-- now show rooms that have no room leading to them

no_exit = {}

for k, v in pairs (rooms) do
 
 if not v.from_room then
   table.insert (no_exit, k)
 end -- if no exit to here

end -- for each room

-- show rooms in order
table.sort (no_exit)

for _, v in ipairs (no_exit) do
   print ("No exit to room", v)
end -- for 


This code checks each room to see if an exit leads to it.

My results (this is from the standard SmaugFuss 1.9 distribution) was:


No exit to room	2
No exit to room	3
No exit to room	4
No exit to room	6
No exit to room	8
No exit to room	9
No exit to room	10
No exit to room	11
No exit to room	12
No exit to room	13
No exit to room	14
No exit to room	15
No exit to room	21
No exit to room	23
No exit to room	26
No exit to room	27
No exit to room	41
No exit to room	82
No exit to room	105
No exit to room	107
No exit to room	199
No exit to room	878
No exit to room	899
No exit to room	1200
No exit to room	1201
No exit to room	2485
No exit to room	2493
No exit to room	2494
No exit to room	2495
No exit to room	2496
No exit to room	2497
No exit to room	2498
No exit to room	2499
No exit to room	3406
No exit to room	3407
No exit to room	3569
No exit to room	3580
No exit to room	3585
No exit to room	3589
No exit to room	6030
No exit to room	6040
No exit to room	6156
No exit to room	6619
No exit to room	6628
No exit to room	6640
No exit to room	6641
No exit to room	6642
No exit to room	6643
No exit to room	6644
No exit to room	6645
No exit to room	6646
No exit to room	6647
No exit to room	6648
No exit to room	6649
No exit to room	6650
No exit to room	6651
No exit to room	7071
No exit to room	7130
No exit to room	7235
No exit to room	7268
No exit to room	7301
No exit to room	7903
No exit to room	8988
No exit to room	8989
No exit to room	8990
No exit to room	8991
No exit to room	8992
No exit to room	8993
No exit to room	8994
No exit to room	8995
No exit to room	8996
No exit to room	8997
No exit to room	8998
No exit to room	8999
No exit to room	10318
No exit to room	10320
No exit to room	10321
No exit to room	10322
No exit to room	10323
No exit to room	10324
No exit to room	10391
No exit to room	10398
No exit to room	10399
No exit to room	10400
No exit to room	10429
No exit to room	10498
No exit to room	10499
No exit to room	21067
No exit to room	21322
No exit to room	21325
No exit to room	21340
No exit to room	21390
No exit to room	21436
No exit to room	21499
No exit to room	24866
No exit to room	24873
No exit to room	24874
No exit to room	24875
No exit to room	24880
No exit to room	24884
No exit to room	24885
No exit to room	24886


No doubt some of those are deliberate, and some rooms are reached by mob programs, which this does not attempt to analyze. However it could be useful for checking if various areas have "useless" rooms.

The next thing to check is analyze our mobs. A preliminary investigation finds the number of mobs of each level, and what class they are:



-------------------- MOBS LIST --------------------------------

mob_levels = {}
classes = {}

-- look for mobs
for mob_vnum, mob_data in pairs (mobs) do
  
  local alignment, level, mobthac0, ac, gold, exp = 
    string.match (mob_data.Stats1, 
       "^([%d-]+)%s+([%d-]+)%s+([%d-]+)%s+([%d-]+)%s+([%d-]+)%s+([%d-]+)$")
    
  if level then
    level = tonumber (level)
    mob_levels [level] = mob_levels [level] or {}  -- make this level if required
    table.insert (mob_levels [level], mob_vnum)
  else
    print ("Mob vnum " .. mob_vnum .. " does not have a level")
  end -- if 

  classes [mob_data.Class] = (classes [mob_data.Class] or 0) + 1
end -- for each room


for level, v in pairsByKeys (mob_levels) do
  print ("Level " .. level .. " - " .. #v .. " mobs.")
end -- for each level

for k, v in pairsByKeys (classes) do
  print ("Mob class: " .. k .. " - " .. v .. " items.")
end -- for each class


My results were:


Level 1 - 101 mobs.
Level 2 - 6 mobs.
Level 3 - 18 mobs.
Level 4 - 15 mobs.
Level 5 - 32 mobs.
Level 6 - 32 mobs.
Level 7 - 21 mobs.
Level 8 - 18 mobs.
Level 9 - 4 mobs.
Level 10 - 47 mobs.
Level 11 - 7 mobs.
Level 12 - 22 mobs.
Level 13 - 10 mobs.
Level 14 - 7 mobs.
Level 15 - 25 mobs.
Level 16 - 5 mobs.
Level 17 - 1 mobs.
Level 18 - 4 mobs.
Level 20 - 16 mobs.
Level 21 - 2 mobs.
Level 22 - 2 mobs.
Level 23 - 2 mobs.
Level 24 - 1 mobs.
Level 25 - 9 mobs.
Level 28 - 1 mobs.
Level 29 - 2 mobs.
Level 30 - 8 mobs.
Level 33 - 1 mobs.
Level 35 - 11 mobs.
Level 37 - 2 mobs.
Level 38 - 2 mobs.
Level 39 - 1 mobs.
Level 40 - 4 mobs.
Level 44 - 1 mobs.
Level 45 - 5 mobs.
Level 50 - 66 mobs.
Level 51 - 2 mobs.
Level 56 - 1 mobs.
Mob class: augurer - 2 items.
Mob class: baker - 1 items.
Mob class: blacksmith - 3 items.
Mob class: butcher - 1 items.
Mob class: cleric - 8 items.
Mob class: druid - 4 items.
Mob class: mage - 87 items.
Mob class: ranger - 3 items.
Mob class: thief - 7 items.
Mob class: vampire - 6 items.
Mob class: warrior - 392 items.


You could use code like this to see if your MUD is "balanced" - for example, above I see only 10 mobs in the level range 40 to 49.

At this stage I haven't done the code to write the area file back out, although I don't think it would be too hard. You could use that to make some sort of "global" change (like giving each mob some extra ability).

The extra file pairsbykeys.lua (used by the require statement) is the same as distributed with MUSHclient. The file tprint.lua is similar to the one that came with MUSHclient, but with Note changed to print, and Tell changed to io.write, as follows:


function tprint (t, indent, done)
  -- show strings differently to distinguish them from numbers
  local function show (val)
    if type (val) == "string" then
      return '"' .. val .. '"'
    else
      return tostring (val)
    end -- if
  end -- show
  -- entry point here
  done = done or {}
  indent = indent or 0
  for key, value in pairs (t) do
    io.write (string.rep (" ", indent)) -- indent it
    if type (value) == "table" and not done [value] then
      done [value] = true
      print (show (key), ":");
      tprint (value, indent + 2, done)
    else
      io.write (show (key), "=")
      print (show (value))
    end
  end
end

return tprint


- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by Nick Gammon   Australia  (22,985 posts)  [Biography] bio   Forum Administrator
Date Reply #1 on Sun 02 Mar 2008 08:59 PM (UTC)
Message
You can look at individual items easily by using tprint, for example:



tprint (mobs [10305])  --> displays:


"Vnum"="10305"
"Stats2"="11 1 0"
"DefPos"="standing"
"Class"="warrior"
"Long"="A naga is here, eyes narrowed in anger
"
"Race"="human"
"Saves"="0 0 0 0 0"
"Stats3"="0 0 0"
"Bodyparts"="head arms legs feet eye"
"Stats4"="0 0 0 0 0"
"Short"="the naga"
"Keywords"="naga cage"
"Speaks"="common"
"Desc"="Not the prettiest creature you have ever seen. This beasts entire lower
half is that of a snake. The upper part of its body is that of a woman.
She does not look happy to see you.
"
"Speaking"="common"
"Attribs"="13 13 13 13 13 13 13"
"Stats1"="0 1 0 94 100 0"
"Position"="standing"
"Actflags"="npc sentinel stayarea prototype"
"Gender"="neuter"


- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by ThomasWatts   USA  (66 posts)  [Biography] bio
Date Reply #2 on Mon 03 Mar 2008 04:43 AM (UTC)
Message
That's really groovy Nick. It could easily be rewritten to import all areas into Lua and then serialize them back out.
Nice work.
[Go to top] top

Posted by David Haley   USA  (3,881 posts)  [Biography] bio
Date Reply #3 on Mon 03 Mar 2008 07:43 AM (UTC)
Message
Yes, this is pretty cool!

One of the reasons why I lobbied to have area files be in Lua format, and not just the key/value format, is so that this kind of analysis could be done without the initial step.

David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone

http://david.the-haleys.org
[Go to top] top

Posted by Nick Gammon   Australia  (22,985 posts)  [Biography] bio   Forum Administrator
Date Reply #4 on Tue 04 Mar 2008 04:50 AM (UTC)
Message
However then their problem would be to get all the data from the Lua script space into the C part (and back again for writing).

On balance, they have probably done it the easiest way, short of a full rewrite.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by David Haley   USA  (3,881 posts)  [Biography] bio
Date Reply #5 on Tue 04 Mar 2008 08:50 AM (UTC)
Message
Well, that's the thing: you don't have to get it from Lua into C. You can just parse the Lua file as if it were your normal key/value pair format, with a slightly different syntax. There's no need to treat it as actual Lua; just use the same syntax.

And also, the Lua format of using braces to indicate nesting makes things much clearer. Much more robust and visually clear than using nesting level integers, too.

The point is that you don't need to read the "Lua" into a Lua state, you can treat it as your key/value format.

David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone

http://david.the-haleys.org
[Go to top] top

Posted by ThomasWatts   USA  (66 posts)  [Biography] bio
Date Reply #6 on Wed 05 Mar 2008 01:17 AM (UTC)
Message
Biggest bonus of using Lua to read in the files if they are in a serialize format is that it is incredibly easy to load the data, and all without using loops.
Also, as David said, it makes alot easier to read.
[Go to top] top

Posted by Samson   USA  (683 posts)  [Biography] bio
Date Reply #7 on Wed 05 Mar 2008 04:53 AM (UTC)
Message
The main reason for having chosen the KEY/VALUE system in FUSS now is because it matches up with the behavior of the rest of the game's data files. Pfiles, deities, clans, system config, etc. are all stored in the same format.

There's also the fact that at the time most folks at the FUSS site were not ready to go all out with Lua, much less use it for the area file format. That may change later, but for now Nick is pretty much right. We went with the best solution that most people were happy with.
[Go to top] top

Posted by David Haley   USA  (3,881 posts)  [Biography] bio
Date Reply #8 on Wed 05 Mar 2008 05:06 AM (UTC)
Message
I think there is still some misunderstanding about how a Lua file format might be used. You don't have to "go all out Lua" if you store your data in a format that happens to be one that Lua can natively read. There is absolutely no need to even touch a Lua interpreter with a 100-foot pole.

David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone

http://david.the-haleys.org
[Go to top] top

Posted by Nick Gammon   Australia  (22,985 posts)  [Biography] bio   Forum Administrator
Date Reply #9 on Wed 05 Mar 2008 07:29 PM (UTC)

Amended on Wed 05 Mar 2008 07:30 PM (UTC) by Nick Gammon

Message
Sounds like I have opened up a bit of a can of worms from the SmaugFuss forums. :)

I can see where David and Samson are both coming from, but I would like to point out to Samson that the problem with the current format is that it can't be read by a "generic" reader. What I means is this, take this example:


#MOBILE
Vnum       10301
Keywords   slug arena~
Short      a slimy slug~
Long       A slimy slug is here scrounging up a meal
~
Race       human~
Class      warrior~
Position   standing~
DefPos     standing~
Gender     neuter~
Actflags   npc stayarea~
Stats1     0 4 0 45 500 0
Stats2     44 0 1
Stats3     0 0 0
Stats4     0 0 0 0 0
Attribs    13 13 13 13 13 13 13
Saves      0 0 0 0 0
Speaks     common~
Speaking   common~
Bodyparts  eye tail~
Immune     charm~
#ENDMOBILE


Half the lines are tilde-terminated, and the other half are not. Without maintaining a list of keywords (like I do on page 1 of this thread) you can't really read this in, because you need to know that "Long" (in particular in this case) spans multiple lines, and that you keep reading until the tilde occurs. Unfortunately you can't just always read until the tilde, because some lines don't have it. Also you can't just read until the newline because some lines span multiple lines, and not just 2 lines either.

A small change could fix that, simply always write the tilde:


#MOBILE
Vnum       10301~
Keywords   slug arena~
Short      a slimy slug~
Long       A slimy slug is here scrounging up a meal
~
Race       human~
Class      warrior~
Position   standing~
DefPos     standing~
Gender     neuter~
Actflags   npc stayarea~
Stats1     0 4 0 45 500 0~
Stats2     44 0 1~
Stats3     0 0 0~
Stats4     0 0 0 0 0~
Attribs    13 13 13 13 13 13 13~
Saves      0 0 0 0 0~
Speaks     common~
Speaking   common~
Bodyparts  eye tail~
Immune     charm~
#ENDMOBILE


Now a generic reader can always read until the tilde. This means that if you upgrade or change area files they will be a lot more compatible with older or future versions - you have a standard that is not dependent on knowing the keywords of which tags are strings and which aren't.

Another problem for a generic reader is that some things are repeated, for example:


Reset M 0 10399 1 10300
Reset D 0 10300 2 1
Reset O 0 10401 1 10300


When I first wrote my reader I made a check that you didn't get duplicate keys (for example, a mob can't have two "Class" lines). However this check fails for things like Reset because you *can* have more than one. Again, we need to have a list of things that can be repeated.

Even without going to a Lua format it would be nice to have some indication that some things should be stored as a list, for example:


Reset+
 Reset M 0 10399 1 10300
 Reset D 0 10300 2 1
 Reset O 0 10401 1 10300
Reset-


Your last problem is that there is no way of saving the "~" character in room descriptions etc., which has reduced your character set by one.

If you were to quote things instead, and "escape" the quote itself, then you could use the full character set, eg.


Desc = "Above you is the entrance to the town of Darkhaven.\
There is a sign saying \"Welcome\"!\
",


Having got this far, maybe the Lua format is just as easy as inventing your own one. Again, as David says, you could simply borrow the format, not actually use an Lua code.

Here is how room 10300 could look in that format, I did this be re-serializing that room from what I read in:


rooms = {
  [10300] = {
    exit_descs = {
      [1] = {
        ExDesc = "You see a solid oak door.\
  ",
        ExDescKey = "door",
        },
      [2] = {
        ExDesc = "You see an oak door.\
  ",
        ExDescKey = "s",
        },
      [3] = {
        ExDesc = "You see a door.\
  ",
        ExDescKey = "south",
        },
      },
    Vnum = "10300",
    mudprogs = {
      [1] = {
        Comlist = "if ispc($n)\
  if level($n) == 1\
  mpforce $n config +brief\
  endif\
  endif\
  ",
        Arglist = "100",
        Progtype = "leave_prog",
        },
      },
    resets = {
      [1] = "M 0 10399 1 10300",
      [2] = "D 0 10300 2 1",
      [3] = "O 0 10401 1 10300",
      },
    Sector = "forest",
    Flags = "indoors safe nosummon",
    Name = "Darkhaven Academy",
    exits = {
      [1] = {
        ToRoom = "10301",
        Direction = "north",
        Flags = "nomob~",
        Desc = "A Stone Corridor\
  ",
        },
      [2] = {
        ToRoom = "10382",
        Direction = "south",
        Flags = "isdoor closed nomob~",
        Keywords = "door~",
        },
      [3] = {
        ToRoom = "21280",
        Direction = "up",
        Desc = "Above you is the entrance to the town of Darkhaven.\
  ",
        },
      },
    Desc = "You stand inside the Darkhaven Academy, an establishment designed to teach\
  the basics of play inside this game.  Each room has a specific purpose and\
  contains information on the various commands to maneuver around and interact \
  with the players.  We recommend you explore the Academy in full, taking the \
  time to read the instructions in each room.\
   \
  Type N to move north to the next room in the Academy.  If you have already\
  completed the rigors of the Academy and wish to test your combat mettle in\
  the battlegrounds, type OPEN S or OPEN DOOR, then S to enter the arena.\
  ",
    }
      
    } -- end rooms


This format isn't particularly hard to write out, and to read back in you just look for "key = value" pairs, separated by commas.

If the value happens to start with "{" then you are going down a level (eg. a list of exits).

You can see from the above example of a room, that we have multiple:


  • Exit descriptions (3)
  • Mud programs (1 in this case)
  • Resets (3)
  • Exits (3)


- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by David Haley   USA  (3,881 posts)  [Biography] bio
Date Reply #10 on Wed 05 Mar 2008 11:05 PM (UTC)
Message
That's a good point, actually: the current key/value format isn't really even key/value, because some pairs are formatted quite differently from others (tilde vs. non-tilde). All in all it's not really a robust, generic format at all. :-(

You can even make the Lua format look very similar to the current format by dropping the external rooms table. If you were to load the data into Lua, you would simply load it in a different "global" environment, thereby setting keys not to globals, but to keys in a table that you use as the mobile.

In Lua, you would have this string:

------------------------
name = "george"
vnum = 123
short = "foobar"
------------------------


you would create a loader function:

loader = loadstring(str)

now you create the environment:

room = {}
setfenv(loader, room)


and finally you call the function with that environment:

loader()


and boom, now you have room.name = "george", etc., without touching your global namespace, and the Lua file looks almost exactly like proper key-value pairs.

David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone

http://david.the-haleys.org
[Go to top] top

The dates and times for posts above are shown in Universal Co-ordinated Time (UTC).

To show them in your local time you can join the forum, and then set the 'time correction' field in your profile to the number of hours difference between your location and UTC time.


25,027 views.

It is now over 60 days since the last post. This thread is closed.     [Refresh] Refresh page

Go to topic:           Search the forum


[Go to top] top

Quick links: MUSHclient. MUSHclient help. Forum shortcuts. Posting templates. Lua modules. Lua documentation.

Information and images on this site are licensed under the Creative Commons Attribution 3.0 Australia License unless stated otherwise.

[Home]


Written by Nick Gammon - 5K   profile for Nick Gammon on Stack Exchange, a network of free, community-driven Q&A sites   Marriage equality

Comments to: Gammon Software support
[RH click to get RSS URL] Forum RSS feed ( https://gammon.com.au/rss/forum.xml )

[Best viewed with any browser - 2K]    [Hosted at HostDash]