Suggested protocol for server to client "out of band" messages

Posted by Nick Gammon on Tue 02 Feb 2010 08:11 PM — 340 posts, 1,061,117 views.

Australia Forum Administrator #0
Introduction


This post describes a suggested way of adding extra information that can be sent from a MUD server to a MUD client to enhance the player's experience.

Players are becoming accustomed, especially when playing MMO games such as World Of Warcraft to be able to quickly see information such as their current health and mana, their location and information about their enemies.

As regular visitors to this forum would know I've experimented with doing this in various ways, some which have been documented on Vimeo.

The two main approaches I've taken so far are:

  • Detecting the standard prompt which arrives from the mud in a trigger and using that to establish the player's health and experience and drawing that in a miniwindow.
  • I have also worked in conjunction with Lasher from Aardwolf to have it send special tags such as {stats} and again write triggers which interpret those and display the information in a window.


However both of those techniques have problems, one of which is if the trigger is not enabled no data is collected and the player can see a lot of meaningless numbers scrawled his screen. Also in the case of the Aardwolf stats it is important to make sure that the server is sending the data down in the first place.

I am aware that there are other protocols used for a similar purpose such as MXP, MCP and ATCP.

My goal with the suggested system here is simply to have a system that is extremely easy to use -- there is no compulsion to adopt it over any other system, however testing shows that works very well and is easy to implement.

Negotiation


The system described here uses telnet subnegotiation. The first step is to establish whether or not the client supports such negotiation sequences. To do that the server starts by sending:


Server: IAC WILL 102 --> Can I send protocol 102 stuff to you?  \xFF \xFB \x66
Client: IAC DO 102 --> Yes, do that (sets a flag in the server) \xFF \xFD \x66

Default behaviour in early versions of MUSHclient:

Client: IAC DONT 102 --> Client does not want telnet option 102 sent to it  \xFF \xFE \x66


Clients that do not support this protocol may ignore the negotiation request in which case nothing further will be sent to it.

[EDIT 6 Feb 2010] - version 4.48 of MUSHclient now supports any reasonable telnet type (eg. instead of 102 you could use 103).


Sending data


To send data from the server to the client you basically put the data between the IAC subnegotiation sequences as follows:


Server:  IAC SB 102 <data> IAC SE  -- that is:  \xFF \xFA \x66 <data> \xFF \xF0


The data is simply "assignment statements that can be parsed by the Lua language".

For example:


<IAC> <SB> <102> tt="status";hp=64;maxhp=1000;mana=145;maxmana=145; <IAC> <SE> 


In C you might send that transaction like this:


send_to_char ("\xFF\xFA\x66 tt=\"status\";hp=64;maxhp=1000;mana=145;maxmana=145; \xFF\xF0", ch);


By using Lua as the data exchange benchmark we do not need to spend a lot of time defining a new protocol as the Lua syntax is well-defined, for example in the book Programming In Lua which is available online.

http://www.lua.org/pil/

You do not need to implement Lua on the MUD server, as it is obviously simple to send data along the lines of "hp=64;maxhp=100;" with a simple sprintf or similar function call.

The advantages of using Lua are:

  • Lua is a well defined, mature language
  • It is widely used (eg. in World of Warcraft scripting)
  • It can be used to send numbers, strings, booleans, and tables (keyed randomly, or by sequential number)
  • Scripts written in Lua at the client end can process the incoming data in a couple of lines of code
  • Even in other languages (such as Perl or Python) it would be simple to parse Lua syntax
  • Lua is already used in clients such as MUSHclient, cMUD, Mudlet, and quite possibly others.
  • Lua tables can express quite complex things, like the hit points for an entire party, or a list of inventory items
  • Lua can easily be added to clients that don't already support it. Lua is free, compact, the source is available, and it is easy to implement.


As an example, in one of the plugins I wrote to demonstrate this system, this is all it takes (in MUSHclient) to process the incoming telnet sequence, break it up into individual variables, and use some of them:


function OnPluginTelnetOption (vars)

  local t = {}; setfenv (assert (loadstring (vars)), t) () -- compile and load into t
  
  if t.tt == "status" then
    current_xp = t.xp
    max_xp = t.maxxp
    draw_bar ()
  end -- if
  
end -- function OnPluginTelnetOption


In this case, the data from the example line above is broken down into variables tt, hp, maxhp, mana, maxmana, xp and maxxp. The plugin checks the tt (transaction type) variable, and if it is the correct type (status) it can then use the xp and maxxp variables to draw an experience bar.

Note the use of setfenv, this makes the variables go into the specified table, rather than global namespace. This protects the script from incoming variables that may happen to clash with existing global variables (such as "print" for example).

The code in C to produce such numbers could be:


char buf[MAX_STRING_LENGTH];
snprintf(buf, sizeof (buf), 
           "\xFF\xFA\x66"         // IAC SB 102
           "tt=\"status\";"       // transaction type: status
           "hp=%d;maxhp=%d;"      // hp points
           "mana=%d;maxmana=%d;"  // mana 
           "xp=%d;maxxp=%d;"      // experience
           "\xFF\xF0",            // IAC SE
         ch->hit,
         ch->max_hit,
         ch->mana,
         ch->max_mana,
         ch->exp,
         ch->max_exp
         );
send_to_char( buf, ch );               


You can see that both producing the data, and processing it, is quite simple.

Handling special characters


In order to be work with MUSHclient, and Lua, certain characters in string data need to be processed specially. In particular, the following characters must be converted:

  • Carriage-return (\x0D) becomes "\r" (in other words, a backslash character followed by "r") *
  • Newline (\x0A) becomes "\n" (in other words, a backslash character followed by "n") *
  • Double-quote (") becomes "\"" (in other words, a backslash character followed by double-quote)
  • Escape (\x1B) becomes "\027" (in other words, a backslash character followed by "027") *
  • IAC (\xFF) becomes "\255" (in other words, a backslash character followed by "255")


I have written a function in C called "fixup_lua_strings" that does that conversion for you, allocating enough memory to hold whatever size string is passed in, with the conversions applied. It also puts quotes around the result, to save you having to do that in your sprintf. This lets you handle things like a mob with quotes in its name, or other unusual character sequences, without worrying about it breaking the negotiation sequence.

The function fixup_lua_strings returns a string allocated with malloc, so you should free it after using it.

* [EDIT 6 Feb 2010] - version 4.48 of MUSHclient no longer needs \0x0D, \0x0A or \0x1B to be escaped, although there is no particular harm in doing it. Note that Lua requires \0x0A to be escaped inside a string literal however, otherwise it considers you have an unterminated string.

Reserved words


The following words are reserved (by Lua), they cannot be used as identifiers:


    and       break     do        else      elseif
    end       false     for       function  if
    in        local     nil       not       or
    repeat    return    then      true      until
    while


Please be aware that Lua is case-sensitive, so hp and HP are different variables. Also only the words "nil" and "false" are considered to be false (unlike C). So for example:


locked=0;


The Lua interpreter would consider the variable "locked" to be true, not false, in a straight "if" test, such as:


if locked then
  -- do whatever
end -- if


Thus to set the variable "locked" to be true or false you would use:

 
locked=true;
locked=false;



Maximum size of message


MUSHclient versions between 4.31 and 4.46 support the incoming telnet 102 message, however are limited to a maximum of 127 characters in the message.

MUSHclient version 4.47 onwards supports a maximum of 5000 bytes in the telnet message. If you are using an earlier version of MUSHclient you can download version 4.47 from:

http://www.gammon.com.au/forum/?id=10044

I am not sure how many characters other clients support, but it seems reasonable to limit individual messages to 2048 bytes (2 Kb). That should be plenty to send "status" information, or even a 25-line room description. For much longer sequences (like an entire player inventory) it is probably better to break the message down into smaller items, such as a "start of inventory" message, individual inventory items, and an "end of inventory" message.

[EDIT 6 Feb 2010] - version 4.48 of MUSHclient supports an indefinite message length (see discussion further on in this thread). Thus you could conceivably send an entire inventory, or list of spells, in a single message.

You can download version 4.48 from:

http://www.gammon.com.au/forum/?id=10051


Suggested message format


I suggest that each telnet sequence contain at least a tt field (transaction type). This helps clients identify the purpose of the message, and lets plugins quickly decide if a particular message is relevant. So far I have used:

  • "version" - sends server version to client
  • "status" - status (hp, mana etc.), effectively what you normally get from a "MUD prompt"
  • "move" - player has moved (ie. changed rooms), gives new location and exits


Once the transaction type is in place, further variables can be set as required. For example, when changing rooms:


tt="move"; room=21056; name="Quills and Parchments"; blind=false; dark=false; exits={north=21047;west=21053;};


Note that the characters ESC (0x1B), RETURN (0x0D), NEWLINE (0x0A) or IAC (0xFF) should not appear in the message. They can be replaced by their Lua equivalents in a string variable as described above. Also, inside a string variable the double-quote character must have a backslash in front of it.

[EDIT 6 Feb 2010] - version 4.48 of MUSHclient no longer needs \0x0D, \0x0A or \0x1B to be escaped, although there is no particular harm in doing it. An alternative way of including IAC is to double it (that is, IAC becomes IAC IAC). Note that Lua requires \0x0A to be escaped inside a string literal however, otherwise it considers you have an unterminated string.

This code will convert a string into the appropriate format, including putting in the leading and trailing quote symbols:


/ Author: Nick Gammon; 2nd February 2010
// Fixup for sending Lua-style strings to client
// Convert: \r \n double-quote IAC and ESC
// Note that Lua expects 0x00 to be sent as \0 however since this is a
// null-terminated string, we won't ever get 0x00
// Also puts quotes around result to save you the trouble of doing it.

char * fixup_lua_strings (const char * sce)
  {
  const char * p;
  char * dest;
  char * pd;
  int count = 3;  // allow for quotes and final 0x00 at end
  unsigned char c;

  // first work out how much memory to allocate
  if (sce)
    {
    for (p = sce; *p; p++, count++)
      {
      c = (unsigned char) *p;
      switch (c)
        {
        case '\r':   // carriage-return
        case '\n':   // newline
        case '"':    // double-quote
          count++;   // becomes \r \n and \"
          break;   
        
        case '\x1B':  // ESC becomes \027
        case '\xFF':  // IAC becomes \255
          count += 3;  
          break;
        
       } /* end of switch on character */
  
      }   /* end of counting */
   }  // if sce not NULL
  
  dest = (char *) malloc (count);
  pd = dest;
  *pd++ = '"';  // opening quote
  
  if (sce)
    {
    for (p = sce; *p; p++)
      {
      c = (unsigned char) *p;
      switch (c)
        {
        case '\r':   // carriage-return
          memcpy (pd, "\\r", 2);
          pd += 2;
          break;
           
        case '\n':   // newline
          memcpy (pd, "\\n", 2);
          pd += 2;
          break;
          
        case '"':    // double-quote
          memcpy (pd, "\\\"", 2);
          pd += 2;
          break;
        
        case '\x1B': // ESC
          memcpy (pd, "\\027", 4);
          pd += 4;
          break;
          
        case '\xFF': // IAC
          memcpy (pd, "\\255", 4);
          pd += 4;
          break;
        
        default:
          *pd++ = c;
           break;  
  
        } /* end of switch on character */
  
       }   /* end of copying */
    }  // if sce not NULL    
  
  *pd++ = '"';  // closing quote
  *pd = 0;      // terminating 0x00
  
  return dest;
}


The code allows for empty strings, and even a string which is NULL (which will be converted to an empty string). Since it doesn't know how long incoming strings might be it allocates space for the returned string on the heap, which must then be freed. eg.


char * sName = fixup_lua_strings (room->name);
char buf [1000];
snprintf(buf, sizeof (buf), 
               "\xFF\xFA\x66"         // IAC SB 102
               "tt=\"move\";"         // transaction type: move
               "name=%s;"             // room name (quoted string)
               "\xFF\xF0",            // IAC SE
               sName
               );
free (sName);  // free memory allocated by fixup_lua_strings             


This technique should always be used for strings, unless you are certain that they won't contain quotes, newlines, etc. (for example, if the strings are hard-coded into the server code).

Tables for repeated elements


The example above shows how you can use a table to hold something that has zero or more occurrences, eg.


 exits={north=21047;west=21053;};


In this case we are sending the visible exits (and the vnum of the rooms they lead to). By using a table we can have zero or more exits. You could choose not to divulge the vnums, perhaps like this:


 exits={north=true;west=true;};


Or, like this:


 exits={"north";"west";};


That code would be interpreted by Lua as a "vector", that is exit 1 is north, exit 2 is west.

Or you could supply more information like this:


 exits={north={vnum=21047;open=true;locked=false};west={vnum=21053;open=false;locked=true};};


The above example uses a table within a table. The higher level table records each exit, and for each table there is another table of information about that exit (vnum, open status, locked status).


Client to server messages


So far I have not implemented client to server messages, but for completeness will suggest a format here:



Negotiation:

Server: IAC DO 102 --> will you send me protocol 102 stuff?   \xFF \xFD \x66
Client: IAC WILL 102 --> I will send you stuff                \xFF \xFB \x66

Client to server:

Client: IAC, SB, 102, <data> IAC, SE --> send data to server  \xFF \xFA \x66 <data> \xFF \xF0


The data would follow similar rules to the server-to-client data, namely be Lua-interpretable assignment statements, include a "tt" variable, have a maximum of 2048 bytes, and have special characters "escaped" as described above.

Suggested uses


The client-to-server messages could be used for "machine-readable" commands (eg. put item 87543 into bag 2343).



General suggestions for the message protocol


  • Once the initial negotiation is complete, the server should send all the messages it is capable of, without further negotiation about whether or not the client is interested in them. Since the messages are not visible to the player anyway, this simplifies things for the player when installing plugins. Once installed, a plugin should "just work" without needing to activate or deactivate features.
  • Messages should be as informative as possible. The days of trying to confuse players by being coy about which room they are in are, I think, long gone. Players want to know where they are and what their status is, and saying stuff like "you are in a room of twisty passages" or "you are bleeding heavily" are just annoying. Better to send a message like: room=56434;hp=500;maxhp=600
  • Because of the free-format nature of messages it should be easy to add further fields later on (eg. you might start with hp and mana, and add later on alignment and dexterity).
  • Once you start using messages you should not take away, or change the meaning of, existing fields, as that may break existing plugins.

Amended on Tue 26 Nov 2013 03:36 AM by Nick Gammon
Australia Forum Administrator #1
The plugin below illustrates using the protocol described above to draw a health/mana/movement bar miniwindow:

Template:saveplugin=Health_Bar_Miniwindow_Telnet
To save and install the Health_Bar_Miniwindow_Telnet plugin do this:
  1. Copy the code below (in the code box) to the Clipboard
  2. Open a text editor (such as Notepad) and paste the plugin code into it
  3. Save to disk on your PC, preferably in your plugins directory, as Health_Bar_Miniwindow_Telnet.xml
    • The "plugins" directory is usually under the "worlds" directory inside where you installed MUSHclient.
  4. Go to the MUSHclient File menu -> Plugins
  5. Click "Add"
  6. Choose the file Health_Bar_Miniwindow_Telnet.xml (which you just saved in step 3) as a plugin
  7. Click "Close"
  8. Save your world file, so that the plugin loads next time you open it.



<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE muclient>

<muclient>
<plugin
   name="Health_Bar_Miniwindow_Telnet"
   author="Nick Gammon"
   id="083960a6b070bb36e8775b1e"
   language="Lua"
   purpose="Shows stats in a mini window"
   date_written="2010-02-01"
   requires="4.47"
   version="1.0"
   save_state="y"
   >
<description trim="y">
<![CDATA[
Install this plugin to show an info bar with HP, Mana, 
and Movement points shown as a bar.

The window can be dragged to a new location with the mouse.
]]>
</description>

</plugin>


<!--  Script  -->


<script>
<![CDATA[

GAUGE_LEFT = 55  -- how far across the gauge part starts
GAUGE_COUNT = 3  -- how many bars we are planning to draw
GAP_BETWEEN_BARS = 3
 
WINDOW_WIDTH = 300   -- width of entire status window
NUMBER_OF_TICKS = 5  -- number of tick marks

FONT_NAME = "Fixedsys"    -- the font we want to use
FONT_SIZE = 9
FONT_ID = "fn"  -- internal font identifier
  
require "movewindow"

function DoGauge (sPrompt, current, max, Colour)

  if max <= 0 then 
    return 
  end -- no divide by zero
  
  -- fraction in range 0 to 1
  local Fraction = math.min (math.max (current / max, 0), 1) 
   
  -- find text width so we can right-justify it
  local width = WindowTextWidth (win, FONT_ID, sPrompt)
  
  WindowText (win, FONT_ID, sPrompt,
                             GAUGE_LEFT - width, vertical, 0, 0, ColourNameToRGB "darkred")

  WindowRectOp (win, 2, GAUGE_LEFT, vertical, WINDOW_WIDTH - 5, vertical + font_height, 
                          ColourNameToRGB "gray")  -- fill entire box
 
  
  local gauge_width = (WINDOW_WIDTH - GAUGE_LEFT - 5) * Fraction
  
   -- box size must be > 0 or WindowGradient fills the whole thing 
  if math.floor (gauge_width) > 0 then
    
    -- top half - fade black to wanted colour
    WindowGradient (win, GAUGE_LEFT, vertical, GAUGE_LEFT + gauge_width, vertical + font_height / 2, 
                    0,  -- black
                    Colour, 
                    2)  -- vertical
    
    -- bottom half - fade wanted colour back to black
    WindowGradient (win, GAUGE_LEFT, vertical + font_height / 2, 
                    GAUGE_LEFT + gauge_width, vertical +  font_height,   
                    Colour,
                    0,  -- black
                    2)  -- vertical

  end -- non-zero
  
  -- show ticks
  local ticks_at = (WINDOW_WIDTH - GAUGE_LEFT - 5) / (NUMBER_OF_TICKS + 1)
  
  -- ticks
  for i = 1, NUMBER_OF_TICKS do
    WindowLine (win, GAUGE_LEFT + (i * ticks_at), vertical, 
                GAUGE_LEFT + (i * ticks_at), vertical + font_height, ColourNameToRGB ("silver"), 0, 1)
  end -- for

  -- draw a box around it
  WindowRectOp (win, 1, GAUGE_LEFT, vertical, WINDOW_WIDTH - 5, vertical + font_height, 
          ColourNameToRGB ("lightgrey"))  -- frame entire box
  
  -- mouse-over information: add hotspot if not there
  if not WindowHotspotInfo(win, sPrompt, 1) then
    WindowAddHotspot (win, sPrompt, GAUGE_LEFT, vertical, WINDOW_WIDTH - 5, vertical + font_height, 
                  "", "", "", "", "", "", 0, 0)
  end -- if
  
  -- store numeric values in case they mouse over it
  WindowHotspotTooltip(win, sPrompt, string.format ("%s\t%i / %i (%i%%)", 
                        sPrompt, current, max, Fraction * 100) )

  vertical = vertical + font_height + GAP_BETWEEN_BARS
end -- function DoGauge

function OnPluginInstall ()
  
  win = GetPluginID ()

  WindowCreate (win, 0, 0, 0, 0, 0, 0, 0)
                 
  -- add the font
  WindowFont (win, FONT_ID, FONT_NAME, FONT_SIZE)
  
  -- see how high it is
  font_height = WindowFontInfo (win, FONT_ID, 1)  -- height

  -- find where window was last time
  windowinfo = movewindow.install (win, 7)
  
  window_height = (font_height * GAUGE_COUNT) + (GAUGE_COUNT * 2 + 1) * GAP_BETWEEN_BARS 
  
    
  WindowCreate (win, 
                 windowinfo.window_left, 
                 windowinfo.window_top, 
                 WINDOW_WIDTH, window_height,  
                 windowinfo.window_mode,   -- top right
                 windowinfo.window_flags,
                 0) 

  -- let them move it around                 
  movewindow.add_drag_handler (win, 0, 0, 0, 0)
  
end -- OnPluginInstall

function OnPluginEnable ()
  WindowShow (win, true)
end -- OnPluginDisable

function OnPluginDisable ()
  WindowShow (win, false)
end -- OnPluginDisable

-- hide window on removal
function OnPluginClose ()
  WindowShow (win,  false)  -- hide it
end -- OnPluginClose

function OnPluginSaveState ()
  movewindow.save_state (win)
end -- OnPluginSaveState

-- here when status changes
--[[
 example line (extra lines added here for clarity): 
 
 tt="status";hp=64;maxhp=1000;mana=145;maxmana=145;
 move=110;maxmove=110;xp=2000;maxxp=370741750;level=2;
 gold=10010;combat=false;dead=false;poisoned=false;
 victim={name="the naga";hp=3;maxhp=11;level=1;};  -- if in combat

--]]

function OnPluginTelnetOption (option)

  local t = {}  -- incoming server variables will go into table t
  setfenv (assert (loadstring (option)), t) () -- compile and load into t
  
  if t.tt ~= "status" then
    return
  end
   
  -- require "tprint"
  -- tprint (t)
  
  local background_colour = ColourNameToRGB "lightgreen"
  if t.dead then
    background_colour = ColourNameToRGB "mistyrose"
  elseif t.combat then 
    background_colour = ColourNameToRGB "rosybrown"
  end -- if
  
  -- fill entire box to clear it
  WindowRectOp (win, 2, 0, 0, 0, 0, background_colour)  -- fill entire box

  -- a green inside border indicates you are poisoned
  if t.poisoned then
    WindowCircleOp(win, 2,         -- draw rectangle
                   0, 0, 0, 0,     -- entire window
                   ColourNameToRGB "olivedrab", 6, 4, -- pen, inside border, width 4
                   0, 1)  -- no brush
  end -- if
    
  -- Edge around box rectangle
  WindowCircleOp (win, 3, 0, 0, 0, 0, ColourNameToRGB "darkgray", 0, 2, 0, 1)

  -- stats don't really matter if you are dead
  if t.dead then
    local dead_message = "<You are dead>"
    local width = WindowTextWidth (win, FONT_ID, dead_message)
    local left = (WINDOW_WIDTH - width) / 2
    local top = (window_height - font_height) / 2
    WindowText (win, FONT_ID, dead_message, left, top, 0, 0, ColourNameToRGB "darkred")
  else
    vertical = 6  -- pixel to start at
  
    DoGauge ("HP: ",   t.hp,    t.maxhp,    ColourNameToRGB "darkgreen")
    DoGauge ("Mana: ", t.mana,  t.maxmana,  ColourNameToRGB "mediumblue")
    DoGauge ("Move: ", t.move,  t.maxmove,  ColourNameToRGB "gold")
  end -- if 
  
  -- make sure window visible
  WindowShow (win, true)

end -- function OnPluginTelnetOption


]]>
</script>

</muclient>


This plugin has an improvement over my earlier status bar plugins - if you hover the mouse over one of the bars an information window pops up showing the exact numbers, eg.:


 HP: 50 / 200 (25%)

Amended on Tue 02 Feb 2010 11:12 PM by Nick Gammon
Australia Forum Administrator #2

Example screen shots.

Not fighting:

Fighting:

Dead:

Amended on Tue 02 Feb 2010 08:21 PM by Nick Gammon
Australia Forum Administrator #3
The plugin below detects the "move" telnet message to show your current room, and exits in a miniwindow:

Template:saveplugin=Room_Location_Telnet
To save and install the Room_Location_Telnet plugin do this:
  1. Copy the code below (in the code box) to the Clipboard
  2. Open a text editor (such as Notepad) and paste the plugin code into it
  3. Save to disk on your PC, preferably in your plugins directory, as Room_Location_Telnet.xml
    • The "plugins" directory is usually under the "worlds" directory inside where you installed MUSHclient.
  4. Go to the MUSHclient File menu -> Plugins
  5. Click "Add"
  6. Choose the file Room_Location_Telnet.xml (which you just saved in step 3) as a plugin
  7. Click "Close"
  8. Save your world file, so that the plugin loads next time you open it.



<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE muclient>

<muclient>
<plugin
   name="Room_Location_Telnet"
   author="Nick Gammon"
   id="00ceb6f44bc36ed815a93950"
   language="Lua"
   purpose="Shows where we are in a miniwindow"
   date_written="2010-02-02"
   requires="4.47"
   version="1.0"
   save_state="y"
   >
<description trim="y">
<![CDATA[
Install this plugin to show an your current room name and exits.

The window can be dragged to a new location with the mouse.
]]>
</description>

</plugin>


<!--  Script  -->


<script>
<![CDATA[

FONT_NAME = "Fixedsys"    -- the font we want to use
FONT_SIZE = 9
FONT_ID = "fn"  -- internal font identifier
  
require "movewindow"

function OnPluginInstall ()
  
  win = GetPluginID ()

  WindowCreate (win, 0, 0, 0, 0, 0, 0, 0)
                 
  -- add the font
  WindowFont (win, FONT_ID, FONT_NAME, FONT_SIZE)
  
  -- see how high it is
  font_height = WindowFontInfo (win, FONT_ID, 1)  -- height

  -- find where window was last time
  windowinfo = movewindow.install (win, 7)
   
end -- OnPluginInstall

function OnPluginEnable ()
  WindowShow (win, true)
end -- OnPluginDisable

function OnPluginDisable ()
  WindowShow (win, false)
end -- OnPluginDisable

-- hide window on removal
function OnPluginClose ()
  WindowShow (win,  false)  -- hide it
end -- OnPluginClose

function OnPluginSaveState ()
  movewindow.save_state (win)
end -- OnPluginSaveState

-- here when location changes
--[[
 example line (extra lines added for clarity): 
 
 tt="move";room=21056;name="Quills and Parchments";
 blind=false;dark=false;
 exits={north=21047;west=21053;};
 
--]]

function OnPluginTelnetOption (option)

  local t = {}  -- incoming server variables will go into table t
  setfenv (assert (loadstring (option)), t) () -- compile and load into t
  
  if t.tt ~= "move" then
    return
  end
  
  local width = 0
  local lines = 1  -- have at least one line
  local dark_message = "It is too dark to see."
  local blind_message = "You are blind!"
  local exits_message = "Exits:"

  local background_colour = ColourNameToRGB "darkkhaki"
  
  if t.dark then
    width = WindowTextWidth (win, FONT_ID, dark_message)
  elseif t.blind then
    width = WindowTextWidth (win, FONT_ID, blind_message)
  else  
    width = WindowTextWidth (win, FONT_ID, t.name)
    if t.exits and next (t.exits) then
      width = math.max (width, WindowTextWidth (win, FONT_ID, exits_message))
      lines = lines + 2
      for k, v in pairs (t.exits) do
        width = math.max (width, 10 + WindowTextWidth (win, FONT_ID, k))
         lines = lines + 1
      end -- for each exit
    end -- if      
  end -- if
  
  WindowCreate (win, 
                 windowinfo.window_left, 
                 windowinfo.window_top, 
                 width + 10, font_height * lines + 10,  
                 windowinfo.window_mode,   -- top right
                 windowinfo.window_flags,
                 0) 

  -- let them move it around                 
  movewindow.add_drag_handler (win, 0, 0, 0, 0)
    
  
  -- fill entire box to clear it
  WindowRectOp (win, 2, 0, 0, 0, 0, background_colour)  -- fill entire box
   
  -- Edge around box rectangle
  WindowCircleOp (win, 3, 0, 0, 0, 0, ColourNameToRGB "darkgray", 0, 2, 0, 1)

  if t.blind then
    WindowText (win, FONT_ID, blind_message, 5, 5, 0, 0, ColourNameToRGB "darkred")
  elseif t.dark then
    WindowText (win, FONT_ID, dark_message, 5, 5, 0, 0, ColourNameToRGB "darkgreen")
  else  
    vertical = 5  -- pixel to start at
    WindowText (win, FONT_ID, t.name, 5, 5, 0, 0, ColourNameToRGB "saddlebrown")
    vertical = vertical + font_height * 2 -- leave blank line
    if t.exits and next (t.exits) then
      WindowText (win, FONT_ID, exits_message, 5, vertical, 0, 0, ColourNameToRGB "black")
      for k, v in pairs (t.exits) do
        vertical = vertical + font_height
        WindowText (win, FONT_ID, k, 15, vertical, 0, 0, ColourNameToRGB "darkolivegreen")
      end -- for each exit
    end -- if
  end -- if 
  
  -- make sure window visible
  WindowShow (win, true)

end -- function OnPluginTelnetOption

]]>
</script>

</muclient>
Amended on Tue 02 Feb 2010 11:13 PM by Nick Gammon
Australia Forum Administrator #4

Example screen shot.

Australia Forum Administrator #5
The plugin below draws an experience bar using the "status" telnet message.

Template:saveplugin=Experience_Bar_Telnet
To save and install the Experience_Bar_Telnet plugin do this:
  1. Copy the code below (in the code box) to the Clipboard
  2. Open a text editor (such as Notepad) and paste the plugin code into it
  3. Save to disk on your PC, preferably in your plugins directory, as Experience_Bar_Telnet.xml
    • The "plugins" directory is usually under the "worlds" directory inside where you installed MUSHclient.
  4. Go to the MUSHclient File menu -> Plugins
  5. Click "Add"
  6. Choose the file Experience_Bar_Telnet.xml (which you just saved in step 3) as a plugin
  7. Click "Close"
  8. Save your world file, so that the plugin loads next time you open it.



<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE muclient>

<muclient>
<plugin
   name="Experience_Bar_Telnet"
   author="Nick Gammon"
   id="81ea2275b48f841799d27e87"
   language="Lua"
   purpose="Shows XP to level"
   date_written="2010-02-02"
   requires="4.47"
   version="1.0"
   >
<description trim="y">
<![CDATA[
Install this plugin to show an how close you are to levelling.
]]>
</description>

</plugin>

<!--  Script  -->


<script>
<![CDATA[

win = GetPluginID ()  -- get a unique name

-- configuration

GAUGE_HEIGHT = 11
NUMBER_OF_TICKS = 20

BACKGROUND_COLOUR = 0x808080
BOX_COLOUR = ColourNameToRGB "dodgerblue"

-- draw the bar here, on getting the status info, or window resize
function draw_bar ()

  -- check numbers for validity
  if not current_xp or  
     not max_xp or
     current_xp < 0 or
     max_xp <= 0 then
     return
  end -- if

  -- cannot have more than max xp
  if current_xp > max_xp then
     current_xp = max_xp
  end -- if
  
 -- width is window width minus 2
 local gauge_width = GetInfo (281) - 2
 
 -- make room for the bar
 local bottom_margin = GetInfo (275)
 
 -- adjust text rectangle, keeping existing settings where possible
 if bottom_margin == 0 or 
    (bottom_margin < 0 and math.abs (bottom_margin) < (GAUGE_HEIGHT + 2)) then
   TextRectangle(GetInfo (272), GetInfo (273),   -- left, top
                  GetInfo (274), -- right
                  - (GAUGE_HEIGHT + 2),  -- bottom (gauge height plus 2 more)
                  GetInfo (276), GetInfo (282) or 0, GetInfo (277),  --  BorderOffset, BorderColour, BorderWidth
                  GetInfo (278), GetInfo (279)) -- OutsideFillColour, OutsideFillStyle
 end -- if
  
 -- make the miniwindow
 WindowCreate (win, 
             0, 0,   -- left, top (auto-positions)
             gauge_width,     -- width
             GAUGE_HEIGHT,  -- height
             10,       -- auto-position: bottom left
             0,  -- flags
             BACKGROUND_COLOUR) 
  
  WindowRectOp (win, 2, 0, 0, 0, 0, BACKGROUND_COLOUR)  -- fill entire box
 
  -- how far through the level we are 
  local done = current_xp / max_xp
 
  -- box size must be > 0 or WindowGradient fills the whole thing 
  if math.floor (gauge_width * done) > 0 then
    
    -- top half
    WindowGradient (win, 0, 0, gauge_width * done, GAUGE_HEIGHT / 2, 
                    0x000000,
                    BOX_COLOUR, 2) 
    
    -- bottom half
    WindowGradient (win, 0, GAUGE_HEIGHT / 2, gauge_width * done, 0, 
                    BOX_COLOUR,
                    0x000000,
                    2) 

  end -- any experience to speak of
  
  -- show ticks
  local ticks_at = gauge_width / NUMBER_OF_TICKS
  
  -- ticks
  for i = 1, NUMBER_OF_TICKS do
    WindowLine (win, i * ticks_at, 0, i * ticks_at, GAUGE_HEIGHT, ColourNameToRGB ("silver"), 0, 1)
  end -- for

  -- draw a box around it
  check (WindowRectOp (win, 1, 0, 0, 0, 0, ColourNameToRGB ("lightgrey")))  -- frame entire box
    
  -- mouse-over information: add hotspot if not there
  if not WindowHotspotInfo(win, "xp", 1) then
    WindowAddHotspot (win, "xp", 0, 0, 0, 0, "", "", "", "", "", "", 0, 0)
  end -- if
  
  -- store numeric values in case they mouse over it
  WindowHotspotTooltip(win, "xp", string.format ("Experience\t%i / %i (%i%%)", 
                        current_xp, max_xp,  current_xp / max_xp * 100) )
  
  -- ensure window visible
  WindowShow (win, true)
  
end -- draw_bar

--[[
 example line (extra lines added here for clarity): 
 
    tt="status";hp=284;maxhp=1000;mana=145;maxmana=145;
    move=110;maxmove=110;xp=2000;maxxp=370741750;
    gold=10010;combat=true;dead=true;poisoned=false;
    victim={name="the naga";hp=3;maxhp=11;level=1;};  -- if in combat
 
--]]


function OnPluginTelnetOption (option)

  local t = {}  -- incoming server variables will go into table t
  setfenv (assert (loadstring (option)), t) () -- compile and load into t
  
  if t.tt == "status" then
    current_xp = t.xp
    max_xp = t.maxxp
    draw_bar ()
  end -- if
  
end -- function OnPluginTelnetOption

function OnPluginWorldOutputResized ()
  draw_bar ()
end -- function
 
-- hide window on removal
function OnPluginClose ()
  WindowShow (win,  false)  -- hide it
end -- OnPluginClose

-- hide window on disable
function OnPluginDisable ()
  WindowShow (win,  false)  -- hide it
end -- OnPluginDisable


]]>
</script>

</muclient>
Amended on Tue 02 Feb 2010 11:22 PM by Nick Gammon
Australia Forum Administrator #6

Example screen shot.

Australia Forum Administrator #7
Changes to SmaugFuss 1.9 source

Below are the "diffs" of the changes I made to the stock SmaugFuss 1.9 source to implement this system:


diff --git a/src/act_info.c b/src/act_info.c
index c097575..5f0ddce 100644
--- a/src/act_info.c
+++ b/src/act_info.c
@@ -35,7 +35,6 @@ bool check_parse_name( const char *name, bool newchar );
 void show_char_to_char_0( CHAR_DATA * victim, CHAR_DATA * ch );
 void show_char_to_char_1( CHAR_DATA * victim, CHAR_DATA * ch );
 void show_char_to_char( CHAR_DATA * list, CHAR_DATA * ch );
-bool check_blind( CHAR_DATA * ch );
 void show_condition( CHAR_DATA * ch, CHAR_DATA * victim );
 
 /*
diff --git a/src/comm.c b/src/comm.c
index 88f0d76..a45bdbf 100644
--- a/src/comm.c
+++ b/src/comm.c
@@ -64,6 +64,8 @@ void shutdown_checkpoint( void );
 #include <netdb.h>
 #endif
 
+#define  MUD_SPECIFIC       '\x66'
+
 #ifdef IMC
 void imc_delete_info( void );
 void free_imcdata( bool complete );
@@ -92,6 +94,7 @@ void free_all_reserved( void );
 const char echo_off_str[] = { ( char )IAC, ( char )WILL, TELOPT_ECHO, '\0' };
 const char echo_on_str[] = { ( char )IAC, ( char )WONT, TELOPT_ECHO, '\0' };
 const char go_ahead_str[] = { ( char )IAC, ( char )GA, '\0' };
+const char want_server_status[] = { ( char )IAC, ( char )WILL, MUD_SPECIFIC, '\0' };
 
 void save_sysdata( SYSTEM_DATA sys );
 
@@ -1037,6 +1040,7 @@ void new_descriptor( int new_desc )
    dnew->ifd = -1;   /* Descriptor pipes, used for DNS resolution and such */
    dnew->ipid = -1;
    dnew->can_compress = FALSE;
+   dnew->want_server_status = false;
    CREATE( dnew->mccp, MCCP, 1 );
 
    CREATE( dnew->outbuf, char, dnew->outsize );
@@ -1086,6 +1090,11 @@ void new_descriptor( int new_desc )
    write_to_buffer( dnew, will_compress2_str, 0 );
 
    /*
+    * extra MUD status
+    */
+   write_to_buffer( dnew, want_server_status, 0 );
+    
+   /*
     * Send the greeting.
     */
    {
@@ -1402,6 +1411,16 @@ void read_from_buffer( DESCRIPTOR_DATA * d )
             else if( d->inbuf[i - 1] == ( signed char )DONT )
                compressEnd( d );
          }
+         else if( d->inbuf[i] == ( signed char )MUD_SPECIFIC )
+         {
+            if( d->inbuf[i - 1] == ( signed char )DO )
+               {
+               d->want_server_status = true;
+               write_to_descriptor(d, "\xFF\xFA\x66tt=\"version\";version=1.0;\xFF\xF0", 0);
+               }
+            else if( d->inbuf[i - 1] == ( signed char )DONT )
+               d->want_server_status = false;
+         }
       }
       else if( d->inbuf[i] == '\b' && k > 0 )
          --k;
@@ -1522,6 +1541,12 @@ bool flush_buffer( DESCRIPTOR_DATA * d, bool fPrompt )
    }
 
    /*
+    * Status as a telnet message
+    */
+    
+   show_status (d->character);
+     
+   /*
     * Short-circuit if nothing to write.
     */
    if( d->outtop == 0 )
@@ -3731,6 +3756,81 @@ void display_prompt( DESCRIPTOR_DATA * d )
    return;
 }
 
+void show_status( CHAR_DATA *ch )
+{
+  
+   if (WANT_TELNET_INFO (ch))
+     {     
+     CHAR_DATA * victim = NULL;
+     
+     if (ch->fighting)
+       victim = ch->fighting->who;
+      
+     char buf[MAX_STRING_LENGTH];
+     snprintf(buf, sizeof (buf), 
+               "\xFF\xFA\x66"         // IAC SB 102
+               "tt=\"status\";"       // transaction type: status
+               "hp=%d;maxhp=%d;"      // hp points
+               "mana=%d;maxmana=%d;"  // mana 
+               "move=%d;maxmove=%d;"  // movement
+               "xp=%d;maxxp=%d;"      // experience
+               "gold=%i;"             // gold
+               "level=%d;"            // level
+               "combat=%s;"           // in combat or not
+               "dead=%s;"             // dead?
+               "poisoned=%s;",        // poisoned?
+               ch->hit,
+               ch->max_hit,
+               IS_VAMPIRE( ch ) ? 0 : ch->mana,
+               IS_VAMPIRE( ch ) ? 0 : ch->max_mana,
+               ch->move,
+               ch->max_move,
+               ch->exp,
+               exp_level( ch, ch->level + 1 ) - ch->exp,
+               ch->gold,
+               ch->level,
+               (ch->fighting && ch->fighting->who) ? "true" : "false",
+               TRUE_OR_FALSE (char_died( ch )),
+               TRUE_OR_FALSE (IS_AFFECTED( ch, AFF_POISON ))
+               );
+  
+      // combat info
+      if (victim)
+        {
+         const char * p = "You";
+         char * pName;
+         
+         if (ch != victim)
+           {
+           if (IS_NPC( victim ) )
+             p = victim->short_descr;
+           else
+             p = victim->name;
+           }
+      
+         pName = fixup_lua_strings (p);
+         
+         snprintf(&buf [strlen (buf)], sizeof (buf) - strlen (buf), 
+                 "victim={name=%s;" // name
+                 "hp=%d;maxhp=%d;"      // hp points
+                 "level=%d;"            // level
+                 "};",
+                 pName,
+                 victim->hit,
+                 victim->max_hit,
+                 victim->level
+               );
+         free (pName); 
+       }
+                   
+     // finish telnet negotiation after the combat info
+     strncpy(&buf [strlen (buf)], "\xFF\xF0", sizeof (buf) - strlen (buf));  // IAC SE
+               
+     send_to_char( buf, ch );
+   }
+  
+} 
+
 void set_pager_input( DESCRIPTOR_DATA * d, char *argument )
 {
    while( isspace( *argument ) )
diff --git a/src/handler.c b/src/handler.c
index 5a1f877..deaa115 100644
--- a/src/handler.c
+++ b/src/handler.c
@@ -49,6 +49,96 @@ void delete_reset( RESET_DATA * pReset );
 int trw_loops = 0;
 TRV_WORLD trw_heap[TRW_MAXHEAP];
 
+// Author: Nick Gammon; 2nd February 2010
+// Fixup for sending Lua-style strings to client
+// Convert: \r \n double-quote IAC and ESC
+// Note that Lua expects 0x00 to be sent as \0 however since this is a
+// null-terminated string, we won't ever get 0x00
+// Also puts quotes around result to save you the trouble of doing it.
+
+char * fixup_lua_strings (const char * sce)
+  {
+  const char * p;
+  char * dest;
+  char * pd;
+  int count = 3;  // allow for quotes and final 0x00 at end
+  unsigned char c;
+
+  // first work out how much memory to allocate
+  if (sce)
+    {
+    for (p = sce; *p; p++, count++)
+      {
+      c = (unsigned char) *p;
+      switch (c)
+        {
+        case '\r':   // carriage-return
+        case '\n':   // newline
+        case '"':    // double-quote
+          count++;   // becomes \r \n and \"
+          break;   
+        
+        case '\x1B':  // ESC becomes \027
+        case '\xFF':  // IAC becomes \255
+          count += 3;  
+          break;
+        
+       } /* end of switch on character */
+  
+      }   /* end of counting */
+   }  // if sce not NULL
+  
+  dest = (char *) malloc (count);
+  pd = dest;
+  *pd++ = '"';  // opening quote
+  
+  if (sce)
+    {
+    for (p = sce; *p; p++)
+      {
+      c = (unsigned char) *p;
+      switch (c)
+        {
+        case '\r':   // carriage-return
+          memcpy (pd, "\\r", 2);
+          pd += 2;
+          break;
+           
+        case '\n':   // newline
+          memcpy (pd, "\\n", 2);
+          pd += 2;
+          break;
+          
+        case '"':    // double-quote
+          memcpy (pd, "\\\"", 2);
+          pd += 2;
+          break;
+        
+        case '\x1B': // ESC
+          memcpy (pd, "\\027", 4);
+          pd += 4;
+          break;
+          
+        case '\xFF': // IAC
+          memcpy (pd, "\\255", 4);
+          pd += 4;
+          break;
+        
+        default:
+          *pd++ = c;
+           break;  
+  
+        } /* end of switch on character */
+  
+       }   /* end of copying */
+    }  // if sce not NULL    
+  
+  *pd++ = '"';  // closing quote
+  *pd = 0;      // terminating 0x00
+  
+  return dest;
+}
+
 TRV_DATA *trvch_create( CHAR_DATA * ch, trv_type tp )
 {
    CHAR_DATA *first, *ptr;
@@ -1780,6 +1870,65 @@ void char_to_room( CHAR_DATA * ch, ROOM_INDEX_DATA * pRoomIndex )
    if( ( obj = get_eq_char( ch, WEAR_LIGHT ) ) != NULL && obj->item_type == ITEM_LIGHT && obj->value[2] != 0 )
       ++pRoomIndex->light;
 
+   if (WANT_TELNET_INFO (ch))
+     {     
+     char buf[MAX_STRING_LENGTH];
+     bool blind = !check_blind( ch );  // true is NOT blind
+     bool dark = !xIS_SET( ch->act, PLR_HOLYLIGHT ) && 
+                 !IS_AFFECTED( ch, AFF_TRUESIGHT ) && 
+                 !IS_AFFECTED( ch, AFF_INFRARED ) && 
+                 room_is_dark( ch->in_room );
+     
+     snprintf(buf, sizeof (buf), 
+               "\xFF\xFA\x66"         // IAC SB 102
+               "tt=\"move\";"         // transaction type: move
+               "blind=%s;"            // character blind?
+               "dark=%s;",            // room dark?
+               TRUE_OR_FALSE (blind),
+               TRUE_OR_FALSE (dark)
+               );
+     
+     // show exits if they are not blinded and it is not dark
+     if (! (blind || dark))
+       {
+       char * pName = fixup_lua_strings (ch->in_room->name);
+       EXIT_DATA *pexit;
+    
+       // if not blind or dark show them room name and vnum   
+       snprintf(&buf [strlen (buf)], sizeof (buf) - strlen (buf), 
+               "room=%d;name=%s;", // vnum, name
+               ch->in_room->vnum,
+               pName
+             );
+       free (pName); 
+       
+       strncpy(&buf [strlen (buf)], "exits={", sizeof (buf) - strlen (buf)); 
+           
+        for( pexit = ch->in_room->first_exit; pexit; pexit = pexit->next )
+         {
+            if( pexit->to_room
+                && !IS_SET( pexit->exit_info, EX_CLOSED )
+                && ( !IS_SET( pexit->exit_info, EX_WINDOW )
+                     || IS_SET( pexit->exit_info, EX_ISDOOR ) ) && !IS_SET( pexit->exit_info, EX_HIDDEN ) )
+            {
+             // WARNING - I assume dir_name gives names that are valid Lua names (which it currently does)
+             //  if not you would need to use fixup_lua_strings and do something like: "[%s]=%i;"
+             snprintf(&buf [strlen (buf)], sizeof (buf) - strlen (buf), 
+               "%s=%i;",          // north=12345
+               dir_name[pexit->vdir],
+               pexit->vnum
+               );
+            }
+         }  // end for each exit        
+       strncpy(&buf [strlen (buf)], "};",  sizeof (buf) - strlen (buf)); 
+       }  // if not blind or dark
+ 
+     // finish telnet negotiation after the exits
+     strncpy(&buf [strlen (buf)], "\xFF\xF0", sizeof (buf) - strlen (buf));  // IAC SE
+     
+     send_to_char( buf, ch );
+   }
+      
    /*
     * Add the room's affects to the char.
     * Even if the char died, we must do this, because the char
diff --git a/src/mapper.c b/src/mapper.c
index b947b8e..0b86ad7 100644
--- a/src/mapper.c
+++ b/src/mapper.c
@@ -55,8 +55,6 @@
 #include "mud.h"
 #include "mapper.h"
 
-bool check_blind( CHAR_DATA * ch );
-
 /* The map itself */
 MAP_TYPE dmap[MAPX + 1][MAPY + 1];
 
diff --git a/src/mud.h b/src/mud.h
index afc197e..e3ff0a0 100644
--- a/src/mud.h
+++ b/src/mud.h
@@ -802,6 +802,7 @@ struct descriptor_data
    unsigned char prevcolor;
    int ifd;
    pid_t ipid;
+   bool want_server_status;
 };
 
 /*
@@ -3235,7 +3236,14 @@ do								\
 #define HAS_BODYPART(ch, part)	((ch)->xflags == 0 || IS_SET((ch)->xflags, (part)))
 #define GET_TIME_PLAYED(ch)     (((ch)->played + (current_time - (ch)->logon)) / 3600)
 #define CAN_CAST(ch)		((ch)->Class != 2 && (ch)->Class != 3)
-
+#define WANT_TELNET_INFO(ch) \
+  (ch != NULL && \
+   !IS_NPC(ch) && \
+   !mud_down && \
+   ch->desc != NULL && \
+   ch->desc->connected == CON_PLAYING && \
+   ch->desc->want_server_status)
+#define TRUE_OR_FALSE(arg) ((arg) ? "true" : "false")
 #define IS_VAMPIRE(ch)		(!IS_NPC(ch)				    \
 				&& ((ch)->race==RACE_VAMPIRE		    \
 				||  (ch)->Class==CLASS_VAMPIRE))
@@ -4320,6 +4328,7 @@ char *format_obj_to_char( OBJ_DATA * obj, CHAR_DATA * ch, bool fShort );
 void show_list_to_char( OBJ_DATA * list, CHAR_DATA * ch, bool fShort, bool fShowNothing );
 bool is_ignoring( CHAR_DATA * ch, CHAR_DATA * ign_ch );
 void show_race_line( CHAR_DATA * ch, CHAR_DATA * victim );
+bool check_blind( CHAR_DATA * ch );
 
 /* act_move.c */
 void clear_vrooms args( ( void ) );
@@ -4439,6 +4448,7 @@ void pager_printf( CHAR_DATA * ch, const char *fmt, ... ) __attribute__ ( ( form
 void pager_printf_color( CHAR_DATA * ch, const char *fmt, ... ) __attribute__ ( ( format( printf, 2, 3 ) ) );
 void act( short AType, const char *format, CHAR_DATA * ch, const void *arg1, const void *arg2, int type );
 const char *myobj( OBJ_DATA * obj );
+void show_status( CHAR_DATA *ch );
 
 /* reset.c */
 RD *make_reset( char letter, int extra, int arg1, int arg2, int arg3 );
@@ -4665,6 +4675,7 @@ int fread_imm_host( FILE * fp, IMMORTAL_HOST * data );
 void do_write_imm_host( void );
 
 /* handler.c */
+char * fixup_lua_strings (const char * sce);
 void free_obj( OBJ_DATA * obj );
 CHAR_DATA *carried_by( OBJ_DATA * obj );
 AREA_DATA *get_area_obj( OBJ_INDEX_DATA * obj );
diff --git a/src/update.c b/src/update.c
index fad7a24..ac6acab 100644
--- a/src/update.c
+++ b/src/update.c
@@ -1279,6 +1279,7 @@ void char_update( void )
          else if( ch == ch_save && IS_SET( sysdata.save_flags, SV_AUTO ) && ++save_count < 10 ) /* save max of 10 per tick */
             save_char_obj( ch );
       }
+      show_status (ch);
    }
    trworld_dispose( &lc );
 }
Amended on Tue 02 Feb 2010 08:37 PM by Nick Gammon
Australia Forum Administrator #8
Debugging plugin

The plugin below can be used by server coders to check exactly what messages they are sending from server to client.

It simply evaluates each incoming message and then "table prints" it.

Template:saveplugin=Telnet_Option_Test
To save and install the Telnet_Option_Test plugin do this:
  1. Copy the code below (in the code box) to the Clipboard
  2. Open a text editor (such as Notepad) and paste the plugin code into it
  3. Save to disk on your PC, preferably in your plugins directory, as Telnet_Option_Test.xml
    • The "plugins" directory is usually under the "worlds" directory inside where you installed MUSHclient.
  4. Go to the MUSHclient File menu -> Plugins
  5. Click "Add"
  6. Choose the file Telnet_Option_Test.xml (which you just saved in step 3) as a plugin
  7. Click "Close"
  8. Save your world file, so that the plugin loads next time you open it.



<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE muclient>

<muclient>
<plugin
   name="Telnet_Option_Test"
   author="Nick Gammon"
   id="7da432fb9f60dc89311475ec"
   language="Lua"
   purpose="Tests OnPluginTelnetOption"
   date_written="2010-02-03"
   requires="4.47"
   version="1.0"
   >

</plugin>

<!--  Script  -->

<script>
<![CDATA[

require "tprint"

function OnPluginTelnetOption (option)
 
 local t = {}  -- incoming server variables will go into table t
 setfenv (assert (loadstring (option)), t) () -- compile and load into t
   
 tprint (t)
   
end -- function OnPluginTelnetOption

]]>
</script>


</muclient>



Example output:


Enter your character's name, or type new: 

"version"=1
"tt"="version"


<42hp 94m 110mv> 

"move"=110
"poisoned"=false
"mana"=94
"maxhp"=43
"hp"=42
"tt"="status"
"combat"=false
"level"=2
"maxxp"=7084
"gold"=900
"xp"=2116
"maxmove"=110
"maxmana"=94
"dead"=false

The kobold grazes you.
The kobold grazes you.

<40hp 94m 110mv> 

"victim":
  "level"=1
  "maxhp"=11
  "name"="the kobold"
  "hp"=11
"move"=110
"poisoned"=false
"mana"=94
"maxhp"=43
"hp"=40
"tt"="status"
"combat"=true
"level"=2
"maxxp"=7084
"gold"=900
"xp"=2116
"maxmove"=110
"maxmana"=94
"dead"=false

west

"room"=10325
"blind"=false
"exits":
  "south"=10340
  "down"=10356
  "west"=10341
  "east"=10342
  "north"=10339
"name"="Chamber of Trials for Warriors"
"dark"=false
"tt"="move"



Effectively you can see what each variable is set to, and any tables are expanded out as well.
Amended on Tue 02 Feb 2010 11:14 PM by Nick Gammon
Australia Forum Administrator #9
The handy thing about these plugins is, that any Smaug (or indeed any style) server that adds the code to it, should then be able to let their players use the plugins on this page without modification.

  • Existing clients that do not recognize the telnet sequence should gracefully decline to use it.
  • If players don't install the plugins they will see nothing as the messages are "out of band" and never shown on the screen.
  • It doesn't matter what the format of the player's prompt line is.
  • It doesn't matter if the player is not even showing their prompt.
  • It is not affected by the problems in the past that triggers did not fire (in MUSHclient at least) until a newline is received after a prompt.
  • Keen plugin writers will be able to enhance the experience by doing things like "damage per second" plugins, or "time to level" plugins.
Netherlands #10
Awesome, Nick. Great to see such an option implemented.

The only thing I am sad about is that your implementation limits two things:

1) People are bound to use TELOPT 102. Which some muds don't use. I know I like my ATCP plugin (or Twisol's, or the other dozen varieties out there people wrote for themselves) but it could be rewritten so much simpler and more efficient if it didn't have to re-parse all input for telnet codes and such. Simply giving codes that weren't implemented by MUSHclient proper to a OnPluginTelnetOption(num, telopt) handler seems like a way more solid idea, since existing games aren't going to change their codes just for MUSHclient.

2) The maximum length limit. Yes, I see the point in your arguments, but also a fact is hard-limiting these things is stupid. It can only block people from using the protocol because they see it as a design flaw that might prevent people from using it. People are only getting MORE bandwidth, so we can't predict what people 10 years from now might want (admit it: stuff barely changes in the telnet/mudding world, so we need to plan ahead).

A strong recommendation is good enough. If it is MUSHclient's memory consumption that worries you, it is simple enough to use a StringBuilder-like object to incrementally allocate the memory and later on feed that to Lua. (This is exactly how I handled telnet negotiation for my mudserver, actually.). If people wrongly using the option is what worries you (using it as a filetransfer mechanism, images, sounds), well, you won't prevent that by forcing them to chunk-size it into smaller bits either. They'll just cut it up a la multi-part email messages or spanned archives, and then nothing is gained except frustration. A good mud owner will hear complaints from users if he puts too much crap in there and lags it to all hell.

This last point about the maximum size I don't write out of practical reasons, but out of 'first client to implement and promote a new standard' point of view (even if it is just plain telnet options + Lua code squashed together). Look at Zmud and MXP: it was constrained from the very beginning by the limitations of that client, effectively crippling it from becoming as good as it could have been. Taking away the size limitation is a trivial matter (I'll gladly donate the code I used for this in my server logic but I doubt you would need it) and might well help the new 'lets use Lua' standard along possible hurdles as it gets adopted.

You have a great initiative going here with Lasher, so please, don't put artificial and arbitrary limits of 5000 bytes or whatever on it. You're the middle-man, talking between MUD and script, and those two will have to decide what is proper to be sent.

(Sorry, this got really lengthy. I just think the limit is pointless to enforce.)
USA #11
I'll fess up straight away that I don't really see the advantage of this over, say, ZMP. I don't know ATCP but apparently it does something kinda similar. But really, this kind of protocol exists, and ZMP at least is quite simple, and it's easier to define semantics in ZMP using the package/command mechanism. (ZMP is also very easy to parse.) It is kind of nice to have Lua be the data format, but I'm not sure I view that as a reason to write yet another protocol to solve this problem, and while it makes implementing things in Lua easier I'm not convinced it really helps other language implementations. Were there specific issues with other protocols that you wanted to address?
USA #12
Worstje said:
1) People are bound to use TELOPT 102. Which some muds don't use. I know I like my ATCP plugin (or Twisol's, or the other dozen varieties out there people wrote for themselves) but it could be rewritten so much simpler and more efficient if it didn't have to re-parse all input for telnet codes and such. Simply giving codes that weren't implemented by MUSHclient proper to a OnPluginTelnetOption(num, telopt) handler seems like a way more solid idea, since existing games aren't going to change their codes just for MUSHclient.

That's a solid suggestion! I'd love to see an OnPluginTelnetOption callback.

David Haley said:
I'll fess up straight away that I don't really see the advantage of this over, say, ZMP. I don't know ATCP but apparently it does something kinda similar. But really, this kind of protocol exists, and ZMP at least is quite simple, and it's easier to define semantics in ZMP using the package/command mechanism. (ZMP is also very easy to parse.) It is kind of nice to have Lua be the data format, but I'm not sure I view that as a reason to write yet another protocol to solve this problem, and while it makes implementing things in Lua easier I'm not convinced it really helps other language implementations. Were there specific issues with other protocols that you wanted to address?

I have to agree with David here, I don't see the benefits of this over ZMP in particular. If you wanted to use Lua as part of the protocol, you could probably define a subpackage in ZMP for it quite easily.
Australia Forum Administrator #13
Worstje said:


2) The maximum length limit. Yes, I see the point in your arguments, but also a fact is hard-limiting these things is stupid.


First things first. :)

I finally found the ZMP spec and notice this on the page:

ZMP spec said:

Commands must be sent using a telnet subnegotiation with the ZMP telnet code. Implementations must be prepared to accept at least 16KiB (16384 bytes) of data per subnegotiation. Implementations should not attempt to send more than 16KiB of data per subnegotiation.


I note that ZMP imposes a limit (admittedly a higher one). I picked the figure of 5000 bytes out of the air for version 4.47 (useful stuff, air), but it could easily be 16384, or indeed, no limit, if you think that imposes unreasonable restraints on implementors.
Australia Forum Administrator #14
By the way, this page uses a new forum code. If you don't see nice headings in various places, refresh your browser to force it to reload the style sheet.
Australia Forum Administrator #15
Twisol said:

I have to agree with David here, I don't see the benefits of this over ZMP in particular. If you wanted to use Lua as part of the protocol, you could probably define a subpackage in ZMP for it quite easily.


I am having trouble finding the full spec for ZMP (not a good sign in itself). I found this page, which seems fairly light on detail:

http://zmp.sourcemud.org/spec.shtml

Another link I found just led to an advertising page (ie. the original had shut down).
Australia Forum Administrator #16
Worstje said:

Awesome, Nick. Great to see such an option implemented.

The only thing I am sad about is that your implementation limits two things:

1) People are bound to use TELOPT 102. Which some muds don't use. I know I like my ATCP plugin (or Twisol's, or the other dozen varieties out there people wrote for themselves) but it could be rewritten so much simpler and more efficient if it didn't have to re-parse all input for telnet codes and such. Simply giving codes that weren't implemented by MUSHclient proper to a OnPluginTelnetOption(num, telopt) handler seems like a way more solid idea, since existing games aren't going to change their codes just for MUSHclient.


Thanks for all the positive feedback everyone.

For a start, the whole project is just a suggestion to kick along the idea of sending more data from MUD servers to clients. The code 102 doesn't have to be used for example, and the limit on message sizes could be removed.

I was thinking of adding support for the ATCP codes (except that for internal reasons MUSHclient doesn't like newlines inside subnegotiation but that can probably be fixed).
Australia Forum Administrator #17
David Haley said:

I'll fess up straight away that I don't really see the advantage of this over, say, ZMP. ... Were there specific issues with other protocols that you wanted to address?


I think I looked at ZMP a while back. The thing that threw me was the design decision to use the null byte (0x00) as an internal terminator. Straight away, for a language-neutral implementation, that made it hard to see how I could capture the incoming text, and pass it to any script language, when many languages use zero-terminated strings to pass strings, and indeed so does MUSHclient in many places.

Now, while MUSHclient has it at a low level (incoming data processing) it is able to handle the 0x00 bytes, but then it has to somehow place an indefinite number of parameters into a table in such a way that any script language could process it. I suppose it could be a VBscript array (like the way it does wildcards) so the problem is not really insurmountable.

Had he chosen *any other character* (eg. 0x01) then it would have been simple to just collect the data as a string and pass it to a plugin (like I do with the OnPluginTelnetOption). Then the plugin (in any language) could break the string down at the delimiter.

I also looked at MCP and thought while I read that "why does everything have to be so complicated?".

Basically my proposed system is *simple*. You don't need to use some new data protocol, you don't need to build up strings with internal 0x00 bytes (you must admit, that in C this rules out using strcat, for example), you don't need to learn a whole new set of rules for transferring data.

Plus, the Lua tables can, when required, be used to simply and easily store things like an entire player's inventory (including stuff in bags), or a room with exits, where you store the attributes of each exit).

However having said that, I don't want to be pig-headed about it. The next version of MUSHclient could:

  • Allow unlimited message lengths
  • Have a callback for ATCP messages
  • Have a callback for ZMP messages


Then you can choose protocol you want. A particular MUD doesn't have to convince the whole community. Provided a couple of mainstream clients support it, players who want the extra features can use them if they want to.
USA #18
ZMP, as far as I can tell, lives on MB:
http://www.mudbytes.net/index.php?a=forum&f=41
Elanthis (the guy who wrote the spec) frequents MB quite a bit, so it's probably the best place to get in touch with him.

AFAIK, ZMP didn't "take off", which is why it's hard to find references to it.

But in all honesty, I think that it -- or some variation of it -- is a better suited protocol for data transfer, due to the fact that semantics are encoded to some extent in the commands themselves. Having things segregated into packages (i.e., namespaces) also makes it fairly easy to provide "ZMP plugins".

Furthermore, data transmission in ZMP is more than just assignment; this can be more natural for commands that are indeed commands or functions, as opposed to simply passing bits of information. I guess what I'm saying is that it is useful to have data transmission in richer forms than assignment.
USA #19
I made my post while you made yours, apparently. :)

Nick Gammon said:
The thing that threw me was the design decision to use the null byte (0x00) as an internal terminator. Straight away, for a language-neutral implementation, that made it hard to see how I could capture the incoming text, and pass it to any script language, when many languages use zero-terminated strings to pass strings, and indeed so does MUSHclient in many places.

I don't really see the issue here. When you get a message, you can split it on the zero string (although admittedly, yes, not using normal string library functions in zero-terminated languages). Then you just pass the arguments as an array of parameters as you would in any other case.

That said, using any other character means you need to escape it; a null byte is extraordinarily unlikely to be needed anywhere. (Then again you might say the same about 0x01, too...)



To be honest I'm not wedded to ZMP, I haven't implemented it in my MUD, etc. But I like the fact that it's not just about passing data, but also passing true commands. To hammer out what we really need, we'd need to go to good old use case analysis. :P
Australia Forum Administrator #20
David Haley said:

ZMP, as far as I can tell, lives on MB:
http://www.mudbytes.net/index.php?a=forum&f=41


Well I had to laugh. There is a lengthy thread there about the maximum message size. Ah well, we go over the same ground again eh? And I note that some of the arguments used are similar to mine (eg. an inventory even, shouldn't be more than 5Kb, but what happens if it is 5Kb + 1?)

elanthis said:

Even a well-written client is still going to have a buffer size limitation, too. Otherwise a server could send IAC SB followed by 2GB of data with no IAC SE and cause bad things to happen (which has nothing to do with ZMP -- it could be _any_ subrequest).


Now, I have for the moment removed any limit - after all, we have a limit or we don't. Now perhaps you and Worstje can fight it out ...

Worstje said:

Look at Zmud and MXP: it was constrained from the very beginning by the limitations of that client, effectively crippling it from becoming as good as it could have been. Taking away the size limitation is a trivial matter

...

please, don't put artificial and arbitrary limits of 5000 bytes or whatever on it.


So, do we want a limit (eg. 1 Mb?) or no limit at all?
Australia Forum Administrator #21
David Haley said:

ZMP, as far as I can tell, lives on MB:
http://www.mudbytes.net/index.php?a=forum&f=41


I still can't see a spec anywhere, apart from what looks like an overview. For example, this is about all I can see about packages:

ZMP spec said:

The package system for ZMP is similar to the idea of namespaces found in popular programming languages, such as C++ and Java. Every command sent over ZMP should belong to a package. A package is simply a name, and a specification of the commands that are available in the package.


That doesn't tell me a heap.

David Haley said:

To be honest I'm not wedded to ZMP, I haven't implemented it in my MUD, etc. But I like the fact that it's not just about passing data, but also passing true commands. To hammer out what we really need, we'd need to go to good old use case analysis. :P


Ah yes, well I was trying to preempt the design phase and leap straight into implementation, like your students would. ;)

I'm not totally sure I see the distinction. After all, the data is "passed" from server to client (or vice-versa). Now I suggested that part of the data be a transaction type (the command, if you like). It is semantics really. We are passing X from A to B. I was deliberately trying to not impose lots of rules about what X is, and if you want to build a command, or many commands (whatever a command is) into X, then you can.

In fact, the mention of namespaces is one of the reasons Lua tables are so attractive. My example on page 1 illustrates that:


"victim":
  "level"=1
  "maxhp"=11
  "name"="the kobold"
  "hp"=11
"maxhp"=43
"hp"=40
"level"=2


Effectively here victim.maxhp is in the victim namespace. What is the difference? The format is so flexible you can use tables as namespaces if you want to.

My major point is that once you start having a lengthy spec and saying you should, or must, use certain things like "packages" then you are imposing a learning curve on developers. Perhaps having a complex system is why ZMP doesn't seem to be widely known.
Australia Forum Administrator #22
David Haley said:

But in all honesty, I think that it -- or some variation of it -- is a better suited protocol for data transfer, due to the fact that semantics are encoded to some extent in the commands themselves. Having things segregated into packages (i.e., namespaces) also makes it fairly easy to provide "ZMP plugins".


Once again, my suggestion of tt field effectively make it what you seem to be calling a package or namespace. A particular message, identified by a tt field, effectively places the rest of the fields into that namespace. So in a "combat" message a "victim" field might mean "the person you are fighting" whereas in a "duel" message the "victim" field might mean "the person you are duelling", or in a "trade" message the "victim" field might mean "the person you are trading with".
USA #23
I'll have to think more about namespaces vs. tables. Something in my gut tells me that the nice thing about the ZMP notion of packages is that it defines namespaces not only for one game, but across games as well. So I can trust that the command "subwindow.print" will have universal meaning; it is the command "print" inside the (well-defined) package "subwindow".
But then again, you can achieve the same thing by having the message be a table called 'print' inside a table called 'subwindow', with the parameters to print inside the 'print' subtable...

I do like the fact that the Lua syntax is used as it makes non-primitive data much easier to pass around. ZMP makes no provision whatsoever for data types, as far as I can remember. This means that you must define your own interpretation for what it means to have subfields, etc. So the protocol here solves that problem, although it only solves it easily for Lua (everybody else has work to do).


Perhaps ZMP was indeed trying to accomplish too much, in defining not only a transport layer but also trying to enforce package specifications shared across all MUDs. I think the idea was that certain things simply make sense for all MUDs, like font colors, sound effects, and so forth, (in essence, replacing MXP, MSP, etc.), and that these should be encoded in a "standard library" if you will so that any MUD can implement ZMP, emit the expected commands, and the client will do the Right Thing.

Your protocol, by only being a data transport layer, makes no provisions for things like this. But I'm not sure if it's the problem you're trying to solve in the first place.


Regarding limits, I think that it's quite difficult to pick a limit and say that it's a hard and fast limit. I think it might be nice to state, before the data, how long the data is going to be, and let the client choose to drop it or not. (You might have some message saying "I ignored your message because it's too long", or something.)
USA #24
On the topic of limits, doesn't HTTP itself state exactly how much will be sent as a header message? That might be a good route to take.

On a personal note, I very much like the namespacing that ATCP/ZMP enforce. Pairing commands with modules/namespaces, and inherently requiring the command to be sent as part of the protocol (as opposed to passing it as part of the data a la your 'tt' parameter), seems to make things less of a bag of data and more of a structured message. What I also like about ZMP in particular is that every parameter is separated by NUL, and it's not hard at all to split by NUL bytes.
Australia Forum Administrator #25
Well I am looking at totally rewriting the IAC subnegotiation code in MUSHclient, to try to keep everyone happy.

Now I have a question about Telnet I can't see to find the answer to...

The subnegotiation goes along these lines:


IAC SB c <some data> IAC SE


Where 'c' it the one-byte protocol code (eg. 102 for the option described earlier, 85 or 86 for MCCP, 90 for MSP and 91 for MXP).

Also, IAC IAC is supposed to be converted to a single IAC. For example:


IAC SB 0xAA "hello" IAC IAC "there" IAC SE


OK, so this means protocol 0xAA is going to be negotiating the message "hello" IAC "there" (as the two IACs turn into one).

Now let's assume we have a state machine, and we are processing one byte at a time (they aren't necessarily in the same packet). This is the problem point here ...

We receive:


IAC SB c <some data> IAC x


Now x has two valid meanings:

  • SE - in which case the subnegotiation is over, and we can process <some data> in the context of type 'c'.
  • IAC - in which case the subnegotiation continues, and we replace IAC IAC by a single IAC


But what about the other 254 cases? What do they mean? For example:


IAC SB c <some data> IAC x   (where x is not SE or IAC)


We have neither a second IAC, or a SE, so this seems to me to be undefined. I can see a few ways this might be handled:

  • Terminate the subnegotiation, assuming we really got:

    
    IAC SB c <some data> IAC SE x
    


    Thus we process <some data> in context "c" and then also process the x byte.
  • Terminate the subnegotiation, assuming we really got:

    
    IAC SB c <some data> IAC SE
    


    Thus we process <some data> in context "c" and then discard the x byte (effectively assuming the x was really SE).
  • Keep the subnegotiation open, assuming we really got:

    
    IAC SB c <some data> IAC IAC x
    


    Thus we are still looking for IAC SE (which may possibly never arrive).
  • Terminate the subnegotiation, but not process <some data> on the grounds that it is an incorrect sequence and deserves to be ignored. However, process the extra byte. This assumes we really got:

    
    x
    

  • Terminate the subnegotiation, but not process <some data> on the grounds that it is an incorrect sequence and deserves to be ignored. In addition, discard the extra byte. This assumes we really got:

    
    <nothing>
    

  • Terminate the subnegotiation, assuming we really got:

    
    IAC SB c <some data> IAC SE IAC x
    


    In other words, in this case we open up a new negotiation (eg. x might be WILL, so we processed "IAC SB c <some data> IAC SE" and now might be getting "IAC WILL y".


So, any telnet experts out there may be able to advise me on the correct approach. :-)
Amended on Wed 03 Feb 2010 10:01 PM by Nick Gammon
Australia Forum Administrator #26
David Haley said:

Regarding limits, I think that it's quite difficult to pick a limit and say that it's a hard and fast limit. I think it might be nice to state, before the data, how long the data is going to be, and let the client choose to drop it or not. (You might have some message saying "I ignored your message because it's too long", or something.)


Now things are getting complex. Instead of a simple sprintf we now have to do the printf first, find out how long the results are, and build that number back into the *start* of the message you already have. And as for "I ignored your message" that assumes a two-way protocol which I wasn't insisting you use. And if you say "I ignored your message" (a response which might arrive a few seconds later) I would ask *which* message did you ignore? In which case you now need to add message sequence numbers. And what does the server do if the client says it ignored the message? Send "whatever" to it?
USA #27
My reading of the spec is that "IAC x" will be ignored unless 'x' is the code for a known command.

So my interpretation of

IAC SB c <some data> IAC x

is that we should keep on processing the subnegotiation, and pretend that we never received the 'IAC x' if 'x' is not a known command.

I'm not sure why 'x' couldn't be any of the several other defined commands, as given at the bottom of http://www.faqs.org/rfcs/rfc854.html. It might be weird, but I guess it's consistent.
USA #28
Nick Gammon said:
Now things are getting complex. Instead of a simple sprintf we now have to do the printf first, find out how long the results are, and build that number back into the *start* of the message you already have. And as for "I ignored your message" that assumes a two-way protocol which I wasn't insisting you use. And if you say "I ignored your message" (a response which might arrive a few seconds later) I would ask *which* message did you ignore? In which case you now need to add message sequence numbers. And what does the server do if the client says it ignored the message? Send "whatever" to it?

Yes, it's complex, and yes, you'd need sequence numbers of some kind. Still, I'm not sure how else you can allow unlimited messages, while still giving the client some control over how much memory it wants to use. As soon as you fix some limit, you run into that good old limit+1 problem. There's little reason to believe why you wouldn't want to send binary image data around, at which point your protocol limit is essentially defining that you can't send images that are "too big". So what do you pick? 500k, 1mb, 3mb, 10mb, 300mb? etc.

The server would probably ignore "your subneg was too long" messages, or perhaps send a (non-subneg) message to the player saying so.

I don't really like the "content length" message in this case, but I don't view it as too terrible, either. It's not really different from what HTTP does.
Netherlands #29
I'm too lazy to piece together all kinds of different quotes, so I'll just reply based on what I remember reading:

Maximum length:
Awesome, no more limit. Maybe it will never be surpassed, but at least we are not stopping any possible use cases, nor complicating those cases where we indeed have 5k+1 characters to transmit.

Seperator byte:
My C implementation of the telnet protocol has zero issues with \0 bytes and allocates memory for telnet sub-negotiation sequences in decent steps thus reasonably optimizing for the smaller cases, but never limiting them. As I said before, if you want it, I can upload it somewhere. (I have optimized the telnet parser as a whole for speed as well so it could save some time in that sense.)

OnPluginTelnetOption callback:
Nick, you spoke of different callbacks for different protocols if I understood you right. I am talking about a single subnegotiation callback for all of the telnet options MUSHclient does not handle itself. Or even better, I'd love it if we could actually take up the choice for the primary negotiation ourselves for such cases. (Not to fall into a repeat affair with my mudserver codebase, but I also implemented a lua API for telnet sub-negotiation in there, keeping proper track of negotiation states and all that jazz.)

The protocol(s) itself:
Let's not fuss about what protocol we are using precisely. Not in the slightest because most plugins will still be quite mud specific, since all muds tend to differ in their gameplay mechanics.

I don't feel ZMP is the perfect solution, since from what I see it has considerable flaws. First of all, it keeps mentioning commands, and I don't see anywhere about plain notifications that do not require a response. I am also afraid it brings along too much of an urge to start doing all the scripted actions in subnegotiation by both mud coders and plugin writers alike, thus moving the actual 'playing' away from the primary channel which really already is a command-response kind of medium. Second, it has limitations on the content-bytes, no NUL bytes and so forth. The telnet protocol has no such limits, although it does ask IACs to be doubled as a form of escaping. Last of all, it seems to have packages and parameters, but beyond that it is all quite unstructured. Essentially there are only string parameters (unless I misunderstand). Nothing like arrays/lists/dictionaries unless you go and hack around a bit. In essence, really simple commands can easily be implemented, once you have anything with a bit more structure you'll be hacking away outside of the spec.

The new Lasher-Nick-Lua protocol also has its issues. Most of all that it IS quite Lua centric. I would prefer to see a straight table be submitted rather than the current different assignments into a setfenv()ed context, because this way, we can gently guide mud authors into writing more compatible messages with other languages. If you allow free-form Lua, you don't only need to worry about sandboxing and such (ok, that seems to be taken care of in your example) but also about the kind of commands you end up allowing. For example, I can imagine some weirdo scripters use for loops, while loops and the like to achieve what could be sent over in a simple table with said loops being done server side - all of which really complicates a Python implementation for example.

However, simply supporting a simplified table syntax would be simpler to implement in such languages. The only limitations that I can think of right away would be 1) no inline function declarations, and preferably 2) no mixing of lists and dictionaries in such tables as that is quite Lua unique. Then you'd get something like (extra whitespace for clarity, and way too specific inventory for the sake of being precise):

<IAC> <SB> <102> {
move=110,
poisoned=false,
mana=94,
maxhp=43,
hp=42,
tt="status",
combat=false,
level=2,
maxxp=7084,
gold=900,
xp=2116,
maxmove=110,
maxmana=94,
inventory = {
   [1] = {
      name = "book",
      desc = "a magical book of Lua wizardry",
      pages = {
          [1] = "First page",
          [2] = "Second page",
          [3] = "Last page already."
      }
   },
   [2] = {
      name = "glasses",
      desc = "some glasses"
   }
},
dead=false
}


Which could also be loaded directly into a variable in Lua (_, blah = loadstring("return "..telopt_data) if I recall the syntax etc correctly). And for Python and other languages, writing a simple parser would be quite elementary (syntax is simple, C parser is available freely also, as are other languages I'm quite sure).

And in the case Lua isn't good enough, there's still other options. XML for the ones with suicidal tendencies (not my choice at all for live data like that, too verbose), but another choice might be JSON. My personal choice would likely be bencoded data (the format used in .torrent files). These last few are already established formats with the kinks worked out of them, and interpreting might be easier for clients due to existing libraries and such.

Either way, my point is... ZMP has flaws. The new LNL (Lasher-Nick-Lua) has flaws. XML has flaws. JSON has flaws, as does Python, VBScript, as well as bencoding. The proper protocol completely depends on the use case as well as the person implementing it.

Did I forget anything? :D Edit: Apparently yes, a small dozen new posts showed up while I was typing. *goes to read those now*
Amended on Wed 03 Feb 2010 10:15 PM by Worstje
USA #30
My only big, overarching problem here is that we seem to be building a protocol because we want to, instead of because it fills a need that's not already filled. We have plenty of handcrafted protocols out there already for exactly that reason, and I'm not so keen on seeing yet another niche protocol added to the mix.
Netherlands #31
Okay, more replies:

Message length:
The HTTP protocol has the Content-Length as an optional field if I recall. You can't trust on it. Also, in this case.. what if the length is shorter than what you encounter? Do you terminate it? How do you deal with the data that you get as a part of subnegotiation and doesn't belong? Ignore it? How about a longer length than the subnegotiation lasts? Skip the subnegotiation and go on good faith? It is ugly.

The memory issues are simple: don't worry about them as long as you allocate in sane chunks at a time. For one, there is virtual memory, and two, if they really send too much, their players will hate them for having the mud freeze on them. You are worrying about a problem that will only exist when you already have far worse problems to worry about.

IAC telnet issue:
Skip the <IAC> <unknown> in the middle of a subnegotiation. I'd throw an error into a notepad page like you do for some other questionable things concerning MXP entities and such. It is what I do.

My mud server source:
I am going to dig it up, extract the relevant bits and pastebin it for you so you can see my implementation since you are rewriting. I like to think it is very much optimized for what it does: handling telnet negotiations.

Netherlands #32
@Twisol:

I disagree. We are not adding a protocol just for the hell of it. Lasher obviously sees a need for a Lua protocol, or he wouldn't be working with Nick as he is implementing it on Aardwolf. All we are doing right now is throwing in suggestions to not make it a half-assed spec that is doomed to fail like so many have (in my eyes, anyhow).
USA #33
I assumed that this was Nick's idea, apologies. However, I still want to know what the other protocols lack, and why it can't be made to work with one of the others.
Australia Forum Administrator #34
David Haley said:

My reading of the spec is that "IAC x" will be ignored unless 'x' is the code for a known command.

So my interpretation of

IAC SB c <some data> IAC x

is that we should keep on processing the subnegotiation, and pretend that we never received the 'IAC x' if 'x' is not a known command.

I'm not sure why 'x' couldn't be any of the several other defined commands, as given at the bottom of http://www.faqs.org/rfcs/rfc854.html. It might be weird, but I guess it's consistent.


Ah, so you are suggesting yet another approach? "pretend that we never received the 'IAC x'" - do you mean keep the negotiation but strip out the 'IAC x', or keep the negotiation and include 'IAC x' in it? (in which case it is like my suggestion of assuming IAC not followed by SE is in fact a single IAC.

I'm not sure about the "if it is a valid command" bit. Let's say we are dealing with some string which may be in German, or UTF-8 or something, where they might validly be "saying" IAC DO 0x46 (which might mean in German "my cat").

Unless you can show in the spec where it says IAC SB c <something> followed by IAC <not IAC> means definitely terminate the sequence and start a new one, with IAC <not IAC> I think this is just guessing.

I've looked at a few pages now, and since they say "including an IAC as part of the string it must be doubled", therefore an IAC not followed by SE is effectively invalid. However my question is, how to recover from that invalidness, short of shutting the client down saying "invalid protocol received, terminating session".
USA #35
Quote:
Awesome, no more limit.

So what if a server starts subnegotiating, and then just sends an endless stream of random bytes? Either the client program will keep on allocating memory until it crashes due to running out of memory (and causing a lot of pain on the computer in the process), or it will have some kind of internal limit anyway in which case the problem hasn't really been solved.

You do have a point, though, that if the MUD server is being malicious, then there are more issues to worry about in the first place.

Quote:
First of all, it keeps mentioning commands, and I don't see anywhere about plain notifications that do not require a response.

Not sure what you're referring to here. Responses aren't required, in general.

Quote:
Last of all, it seems to have packages and parameters, but beyond that it is all quite unstructured.

I think that's kind of the idea. The idea is that you define your packages, commands and parameters on top of the data transport layer.

Quote:
Nothing like arrays/lists/dictionaries unless you go and hack around a bit. In essence, really simple commands can easily be implemented, once you have anything with a bit more structure you'll be hacking away outside of the spec.

Yes, this is an issue with the ZMP spec. It's worth noting that in many cases, lists can be passed as "the x^th argument onwards".

Quote:
And for Python and other languages, writing a simple parser would be quite elementary (syntax is simple, C parser is available freely also, as are other languages I'm quite sure).

This still doesn't really solve the problem of figuring out that it's not a dict but a list, for example.
Australia Forum Administrator #36
Worstje said:

@Twisol:

I disagree. We are not adding a protocol just for the hell of it. Lasher obviously sees a need for a Lua protocol, or he wouldn't be working with Nick as he is implementing it on Aardwolf.


Actually Lasher wanted to use JSON. I note you said "JSON has flaws".

I am really throwing this out as a personal project since I tend to find that once you start negotiating a design document you spend months doing it, don't reach agreement, and nothing is done.

I am certainly willing to modify MUSHclient to improve telnet negotiation (and indeed, use your suggestion Worstje of handing off *all* subnegotiations to a plugin).

My project on page 1 of this thread, which will probably be improved after listening to everyone's comments, is intended to demonstrate a *working* system at both the client and server end.
Amended on Wed 03 Feb 2010 10:39 PM by Nick Gammon
USA #37
Quote:
Ah, so you are suggesting yet another approach? "pretend that we never received the 'IAC x'" - do you mean keep the negotiation but strip out the 'IAC x', or keep the negotiation and include 'IAC x' in it? (in which case it is like my suggestion of assuming IAC not followed by SE is in fact a single IAC.

My proposal was to keep everything and simply strip out 'IAC x', yes.

Quote:
I'm not sure about the "if it is a valid command" bit. Let's say we are dealing with some string which may be in German, or UTF-8 or something, where they might validly be "saying" IAC DO 0x46 (which might mean in German "my cat").

I'm not sure what you're referring to here to be honest. Are you saying that in UTF-8, you might be sending characters that happen to look like the beginning to a valid IAC+command sequence?

Quote:
I've looked at a few pages now, and since they say "including an IAC as part of the string it must be doubled", therefore an IAC not followed by SE is effectively invalid. However my question is, how to recover from that invalidness, short of shutting the client down saying "invalid protocol received, terminating session".

If push came to shove, I would probably terminate the subnegotiation and pretend that everything after that is normal output, and complain to the user (who would then presumably complain to the MUD author). You might get a lot of gibberish until the telnet subneg stopped, but at least after that you'd be in a sane state again.

If the MUD needs to send the character IAC (as part of UTF-8 or whatever) then they need to follow the normal rules of escaping it.
Australia Forum Administrator #38
David Haley said:

I'm not sure what you're referring to here to be honest. Are you saying that in UTF-8, you might be sending characters that happen to look like the beginning to a valid IAC+command sequence?


Well, yes. But it doesn't have to be UTF-8. Someone might be sending a PNG image that is a map. Now given that they *should* convert IAC (0xFF) to IAC IAC the issue shouldn't arise. But what if they don't?

It seems to me, unless you can find a spec that says:


IAC SB c <data> IAC x


Where x it not IAC and not SE, is to be definitely treated as:


IAC SB c <data> IAC SE IAC x


... then we are just in the dark, and trying to work it out based on what x is, is just wishful thinking. If the sequence is undefined, then potentially any treatment of it is wrong.

Following your suggestion of dropping "IAC x" and pressing on, may potentially gives us our 1 Mb of data in the string variable, as the server really meant to terminate the negotiation, and is now sending down a lengthy room description.

However *not* dropping the negotiation might be just as bad, as the player may then see a screenfull of gibberish.

I think I would drop the negotiation, as seeing gibberish and complaining to the MUD admins is probably preferable to having the client "lock up" and having them complain to me. After all, the MUD admins in this case are to blame, not me.
Amended on Wed 03 Feb 2010 10:49 PM by Nick Gammon
Netherlands #39

Malformed IACs in sub-negotiation:
To my knowledge, the telnet protocol works on a byte level. At that point, you have no worries about the used code page at all. As such, any multi-byte character sequences need to be consider binary data like anything else, thus any IACs as part of them need to be doubled. There is no room for confusion there, unless I am misinformed in which case I do appreciate a link setting me straight. :)

I vote for: 'ignore the malformed IAC and the byte following it, and keep processing afterwards'. It seems closest to the spec, which to my knowledge never says malformed IACs can be a reason for bailing on the sub-negotiation. However, I do see the point with people worrying about an invalid state forever clogging the mud. But you already have a World Configuration->Info window which has information about MCCP, MXP and so forth. It should likely always be at the same state of doing nothing on a telnet level. If it were to show anything other than that, you can raise a little bell as to say 'stuff might be wrong here'. Or use another method to inform the user - there are plenty.

Mud server code: http://mushclient.pastebin.com/f469bb629
I think it has all the most important parts in it. If you want the subnegotiation parts as well, let me know.
Netherlands #40
@ David Haley:

Sorry, missed your raised point about the endless stream of junk until just now. You'd have no other issue than you do right now, I believe. The client wouldn't lock up (you can send a ton of stuff down the pipeline and while MUSHclient might become a bit slower, it wouldn't totally lock up), at most the problem of running out of memory again at which point Windows will complain big time, likely increase the pagefile and throw a popup etc first.
Amended on Wed 03 Feb 2010 10:57 PM by Worstje
Australia Forum Administrator #41
Worstje said:

It seems closest to the spec, which to my knowledge never says malformed IACs can be a reason for bailing on the sub-negotiation.


The spec seems to be silent on that point. Since it says an imbedded IAC *must* be doubled, a single one seems to be outside the spec.

Anyway, what is the justification for throwing away the IAC x?

Say I have:


IAC SB c 1 2 3 IAC 4 5 6 IAC SE


You are saying treat that as "1 2 3 5 6". Why not "1 2 3 4 5 6"? Or "1 2 3 IAC 4 5 6"?

Saying the spec does NOT say to NOT do something is not the same as saying the spec says to DO something. If the spec ignores a point, I could just as easily say the spec "never says" that malformed IACs are a reason to *continue* the subnegotiation.

Netherlands #42
Fair point. :) I see the logic in dropping it alltogether out of a client perspective and urging MUD creators to fix their mistakes. In practice, it is likely the best course of action and I won't hold it against you if that is the path taken. It does make me scared about people beginning to use it as a de-facto termination though, and no more usage of the final IAC SE whatsoever. As such, I would recommend a Notepad error, or a script error, or a popup, or SOME kind of error to make it clear to the user that the mud is doing stuff wrong, rather than allow mud authors to live in a grey area of 'hey it works in MUSHclient, there are no errors, so XXX client is at fault'. In my experience, many mud authors are all about adding features, but rarely implement them correctly, and only implement them to the point it works on their own client.

My reason for having the opinion I have, though, is that (again, going on memory) the spec also doesn't mention IAC codes CAN'T happen inside a sub-negotiation sequence. I believe that in practice, most of the useful stuff ends up being done in sub-negotiation, but I think there's a few things that aren't handled in sub-negotiation too, although I could be mistaken.

Are you handling anything other than the IAC WILL/DO/WONT/DONT and IAC SB in the standard state? If you don't, I'll completely agree with you that keeping it is probably useless, since if you don't have such codes supported in MUSHclient after this many years, they probably aren't being used anymore at all.
Australia Forum Administrator #43
Worstje, I looked at your code and am gratified to see that you identified the "third" case:


    else    /* received IAC during subnegotiation */
    {
        if (*p == IAC)   /* double IAC == single IAC */
        {
            chain_appendchar(d->buffer_telnet, IAC);
            d->state_telnet = d->state_telnet - STATE_SB_IAC_DELTA;
        }
        else if (*p == SE)
        {
            /* Process subnegotiation */
            telnet_gotsubnegotiation(L, d,
                (d->state_telnet - STATE_SB_IAC_DELTA),
                d->buffer_telnet);
            chain_reset(d->buffer_telnet);
            d->state_telnet = STATE_PLAIN;
        }
        else
        {
            debugf("process_chunk: came across SB+IAC+%d", *p);
            d->state_telnet = d->state_telnet - STATE_SB_IAC_DELTA;
        }
    }

Australia Forum Administrator #44
Worstje said:

Are you handling anything other than the IAC WILL/DO/WONT/DONT and IAC SB in the standard state? If you don't, I'll completely agree with you that keeping it is probably useless, since if you don't have such codes supported in MUSHclient after this many years, they probably aren't being used anymore at all.


No, but the state machine is more complex than that because it also handles colour sequences (eg. ESC something), and MXP sequences (eg. <bold>).

Anyway, things have worked so far, I agree, so it is probably a bit academic. Interesting that there is no really well defined way of handling it.
Netherlands #45
Well, wouldn't you know, I did terminate it. Hahaha. This amuses me to no end, having my own code speak against my opinion in this topic.

I must have had the same logic as you do and implemented it without really giving it as much thought as I did in this discussion. Or maybe I added it during debugging for the hell of it. I really don't recall anymore, I wrote it many months ago. But still, quite entertaining indeed.
Netherlands #46
Nick Gammon said:

Worstje said:

Are you handling anything other than the IAC WILL/DO/WONT/DONT and IAC SB in the standard state? If you don't, I'll completely agree with you that keeping it is probably useless, since if you don't have such codes supported in MUSHclient after this many years, they probably aren't being used anymore at all.


No, but the state machine is more complex than that because it also handles colour sequences (eg. ESC something), and MXP sequences (eg. <bold>).

Anyway, things have worked so far, I agree, so it is probably a bit academic. Interesting that there is no really well defined way of handling it.


Hrm, you have all those in the same loop? I suppose I can see the point of doing escape sequences in the same level, seeing how it is also a byte-level state machine, but I'd expect the MXP stuff to be quite seperate. Either a level deeper, or abstracted away. I don't recall how it works precisely anymore (sub-negotiation, pure tags in the plain output stream, I don't recall), but either way, I doubt MXP has any place in the core processing loop, but ought to be refactored out of it as much as possible. Or am I missing or misunderstanding something here?

Simplicity is key, and as you probably saw in my code, I want the code to be as self-documenting as possible. MCCP handling, MXP handling, IAC EOR/GA, they are all handled by the same few functions away from the inner loop.

Edit: Btw, a bug in your forum. Look at the post I am quoting. It has the date, but it is lacking the time completely.
Amended on Wed 03 Feb 2010 11:20 PM by Worstje
USA #47
Worstje said:

Edit: Btw, a bug in your forum. Look at the post I am quoting. It has the date, but it is lacking the time completely.


I see the time fine. O_o
Netherlands #48
Ok, here's a link with a screenshot: http://qs.merseine.nu/images/gammon_post_lacks_the_time.png
Australia Forum Administrator #49
Worstje said:

Hrm, you have all those in the same loop? I suppose I can see the point of doing escape sequences in the same level, seeing how it is also a byte-level state machine, but I'd expect the MXP stuff to be quite seperate.


Yes I am. Initially the loop was just the colour codes, and then I think the telnet stuff got added, and eventually MXP. Remember MXP was initially described as "a way of marking up the text" in a similar way to colour codes, and it was supposed to be done on the fly, like colour codes.

Worstje said:

Edit: Btw, a bug in your forum. Look at the post I am quoting. It has the date, but it is lacking the time completely.


Seems OK to me, is it close to midnight with your time correction? It might be zero and suppressed by the time-printing function.
USA #50
Here's what I see. It's undeniably a bug though! http://img69.imageshack.us/img69/8449/gammontime.jpg

It shows 3:00, so Nick might have an excellent theory with that time correction thing.
Netherlands #51
Yup it must be the time correction thing. It is midnight according to that indeed. Silly that I didn't notice. :)

Back on topic though: If you are indeed rewriting the internals a bit to go along with the changed times, I look forward to it. It isn't really a bottleneck, but any optimizations are welcome for those running MUSHclient on older computers. So many features get added, so likely those people can use every bit of optimization possible.
Amended on Wed 03 Feb 2010 11:44 PM by Worstje
Australia Forum Administrator #52
On file the time is: 2010-02-03 23:00:16

Your time correction is 1, so that would make it 24:00:16, shown to the nearest minute.

Ah, lol, I thought it was a MySQL bug where it wouldn't show midnight correctly, but guess what I found in the forum code?


   // get rid of postings at exactly midnight
    if (substr ($formatted_date, -8) == "12:00 AM")
      $formatted_date = substr ($formatted_date, 0,
                                strlen ($formatted_date) - 8);


Now I wonder why I did that?
USA #53
D'oh! *laughs*
Australia Forum Administrator #54
Worstje said:

If you are indeed rewriting the internals a bit to go along with the changed times, I look forward to it. It isn't really a bottleneck, but any optimizations are welcome for those running MUSHclient on older computers. So many features get added, so likely those people can use every bit of optimization possible.


It won't really be optimized, it will just be ... better. More flexible. More extensible. Easier to work with.
Australia Forum Administrator #55
Twisol said:

D'oh! *laughs*


That wasn't a bug - it was By Design.

Can't quite think why it was by design but there you are.
Netherlands #56
That's awesome. Simply awesome. Were you using version control for the forum since the start? Maybe you can backtrack it to a date and look in the forum for bug reports etc around that date.
USA #57
Nick Gammon said:
It won't really be optimized, it will just be ... better. More flexible. More extensible. Easier to work with.


On a similar train of thought, is there any chance we would take advantage of the wiki every GitHub project has, and use it as a sort of to-do list of things that need to be done, are in progress by someone, or would just be great to have? I'd love to contribute to something that's actually wanted.

(Also, any chance we could get the project that builds the resources DLL (i.e. EN.DLL) uplaoded to GitHub too?)
Amended on Thu 04 Feb 2010 12:00 AM by Twisol
Australia Forum Administrator #58
David Haley said:

I'll fess up straight away that I don't really see the advantage of this over, say, ZMP. I don't know ATCP but apparently it does something kinda similar. But really, this kind of protocol exists, and ZMP at least is quite simple, and it's easier to define semantics in ZMP using the package/command mechanism. (ZMP is also very easy to parse.)

...

Were there specific issues with other protocols that you wanted to address?


Twisol said:

I have to agree with David here, I don't see the benefits of this over ZMP in particular. If you wanted to use Lua as part of the protocol, you could probably define a subpackage in ZMP for it quite easily.


I think you guys are answering your own questions here:

David Haley said:

ZMP makes no provision whatsoever for data types, as far as I can remember. This means that you must define your own interpretation for what it means to have subfields, etc.


Worstje said:

I don't feel ZMP is the perfect solution, since from what I see it has considerable flaws.

First of all, it keeps mentioning commands, and I don't see anywhere about plain notifications that do not require a response.
….
Second, it has limitations on the content-bytes, no NUL bytes and so forth.
...
Either way, my point is... ZMP has flaws. The new LNL (Lasher-Nick-Lua) has flaws. XML has flaws. JSON has flaws, as does Python, VBScript, as well as bencoding.


Let's assume every system has some flaws, and at this stage I am trying to reduce any in my proposed system (e.g. message limits, handling of subnegotiation), and offer up simplicity and ease of use as an argument for it.
Australia Forum Administrator #59
Worstje said:

That's awesome. Simply awesome. Were you using version control for the forum since the start? Maybe you can backtrack it to a date and look in the forum for bug reports etc around that date.


I can only guess that when I uploaded the old posts years ago, some didn't have times on them, so they all appeared to be posted at midnight, which looked funny.
Australia Forum Administrator #60
Twisol said:

(Also, any chance we could get the project that builds the resources DLL (i.e. EN.DLL) uplaoded to GitHub too?)


That can be done pretty soon.
USA #61
Nick Gammon said:
Anyway, what is the justification for throwing away the IAC x?

The spec says pretty clearly that IAC means "interpret the next byte as a command". My reading is that, if you don't know what the command is, you just ignore the command; you wouldn't blow up, or treat it as the 'IAC' byte. After all, if you tell me: "David: do the task", I know to do the task, but if you say: "David: do the flipperflop foo", I'll have no idea what you are talking about and will ignore you. :P (Well, actually, I'd ask you what you meant, but that's the nice thing about being able to actually communicate...)

Nick Gammon said:
Let's assume every system has some flaws, and at this stage I am trying to reduce any in my proposed system (e.g. message limits, handling of subnegotiation), and offer up simplicity and ease of use as an argument for it.

I don't think it's all that simple if you're not working in Lua on the receiving end. For example, you also now have the problem I mentioned to Twisol regarding PPI, where a Lua list-table looks like a map to Python, even though it should be creating it as a list object.

I think it's very simple and easy to use if you're working in Lua on the receiving end.

I think it makes it more difficult to standardize messages across MUDs, because that would require another layer of standardization on top of this. (ZMP provides that layer explicitly with the package mechanism, although you still obviously have to write a package spec.)
Of course, I'm not necessarily convinced that it's realistic to expect some kind of large, cross-MUD standardization effort. So, eh. Maybe this doesn't matter.


Somebody brought up the problem of a too-loosely defined protocol data format: "assignments that Lua can handle". Does this mean that you can assign arbitrary expressions, including function calls? What if you sent:

<IAC> <SB> tt="foo";hp=math.pow(2,2) <IAC><SE>

what is the expected value of hp: "math.pow(2,2)" or 4?

IMO this is a pretty serious issue.


To be clear, my statement is not that this new protocol is not good. It's that we're doing something that might already have been done. We could come up with many variations of protocols that are nice etc., repeating what's been done. (For example, we could have sent Python code, not Lua code.)
Actually I kind of like the protocol because using Lua really does make things nice and easy (if you're using Lua). 'course, I have no objection to forcing Lua. :-)
Amended on Thu 04 Feb 2010 03:26 AM by David Haley
Australia Forum Administrator #62
I've done more reading, and still can't see a definitive response. I understand your point about IAC, and it makes sense in many ways to say "IAC will start a new command".

But then why insist on a subnegotiation ending with IAC SE and not just SE (subnegotiation end?).

I suppose the answer is that you may want SE inside the negotiation (for example, with NAWS your window might be 240 characters wide). Thus, you precede it with IAC, and it already deals with IAC being in the sequence because you have to double it.

But in the same way you can say "well, SE on its own is just 240) then you could argue IAC on its own (not followed by a SE) is just IAC. But then maybe not. Maybe you say IAC on its own (not followed by IAC) terminates the subnegotiation. But why use SE at all then?

I suppose the answer to *that* is, until you see another byte after the IAC you don't know if it will be IAC IAC or not.

So if I sent:


IAC SB 102 <blah blah> IAC


I can't do anything yet, because the final IAC might be followed by another one in another packet. So the SE in this case is a sort of "padder" to make sure you know it is *not* an IAC after the IAC.

So I think I can make out a case that the IAC at the end is not Interpret As Command, because for one thing it could be followed by IAC, and for another it could be followed by SE which is "end of subnegotiation" not a "command" of some sort.

As for math.pow, as I suggested you interpret the script in its own environment, so it is a totally empty environment. No functions are there, no _G variable, nothing.
Amended on Thu 04 Feb 2010 03:51 AM by Nick Gammon
Australia Forum Administrator #63
Now I have a question for the ATCP experts.

The next issue that arises from having MUSHclient call a plugin with *any* subnegotiation is, which ones should it agree to handle?

Eg. I notice that the ATCP spec says the server sends:


IAC DO ATCP       (255 253 200)


and the client should reply:


IAC WILL ATCP     (255 251 200)


Fair enough. But if I automatically handle all subnegotiations then should I also agree to the IAC DO ATCP, even if no plugin is installed that will use it?

So one possibility is to agree to all sub-negotiation requests, as in "yeah, yeah send me the data, whatever".

The other is for a plugin to have to agree to it. eg.


function OnPluginTelnetDoRequest (which)
  if which == 200 then
     return true
  else
     return false
  end -- if
end -- function


Now this introduces the issue of the problem of plugins being constantly reinstalled during testing, and they may only get the message during MUD connection time.

So do you think it would be an issue to agree to all requests (or all in a range, and if so, what range?) or make the plugins agree individually?
USA #64
There are some excellent posts by elanthis on the MudBytes forums on the subject of parsing Telnet sequences. Here are a couple:

[1] http://www.mudbytes.net/index.php?a=topic&t=873&p=13651#p13651
[2] http://www.mudbytes.net/index.php?a=topic&t=1335&p=20765#p20765

The important lines, taken from [1], are:

Quote:
IAC : SB -> SB (begin sub-request)
SB : IAC -> SB_IAC (sub-request end/escape sequence)
SB_IAC : IAC -> SB (escaped IAC byte)
SB_IAC : SE -> TEXT (completed subrequest, process data)
SB_IAC : anything -> TEXT (invalid sub-request, abort)

He's basically saying that if you hit an IAC followed by a non-IAC/SB byte, abort the subnegotiation and move back to the text state.
Australia Forum Administrator #65
Oh, and does ATCP include 0x00 inside its sequences?
USA #66
The most recent public documentation for ATCP can be found here [1]. It doesn't make any mention of the NUL byte, but having worked with it for a while (though admittedly only on the client side), I doubt they would allow NUL bytes within the protocol.

When it comes to deciding how to support the protocols, I thought about this for a while when brainstorming my [still theoretical] webclient. I would rather support protocols only through plugins than build them directly into the client. My plan was to let registered MUD owners determine the 'protocol pipeline' for users playing their MUD from my client. Obviously, the idea doesn't translate perfectly to MUSHclient, but I think it's infinitely more extensible to modularize the protocol support. I would be happy with a general subopt mechanism that any 'protocol plugin' can hook into, implementing the processing for that protocol and making the details accessible, just like my ATCP plugin handles it. With MUD-variable protocols like ATCP and ZMP, I don't think the client should be directly responsible for it, especially when there are many potential protocols around. (Of course, MXP and ANSI-colors don't count, as they're rather popular and well-known.)

[1] http://www.ironrealms.com/nexus/atcp.html
Amended on Thu 04 Feb 2010 04:02 AM by Twisol
USA #67
Nick Gammon said:
So I think I can make out a case that the IAC at the end is not Interpret As Command, because for one thing it could be followed by IAC, and for another it could be followed by SE which is "end of subnegotiation" not a "command" of some sort.

I actually think that SE is a command, namely the command to terminate subnegotiation. If you prefer, IAC should be interpreted as "interpret as control <sequence>".

Of course, I'm also quite happy to abort the subneg immediately, on the assumption that it's obviously confusing and improper.

Nick Gammon said:
As for math.pow, as I suggested you interpret the script in its own environment, so it is a totally empty environment. No functions are there, no _G variable, nothing.

Well, ok. If you insist :P
tt="foo";hp=(function(x) x * 7 end)(2)

What is the value of hp? 14? Something else?

What about:
tt="foo";hp=(function(x) while true do x = x end end)(2)

oh no!

Basically, I think you've left the definition for legal values too open by saying it's just any assignable expression in Lua.
Australia Forum Administrator #68
Twisol said:

The most recent public documentation for ATCP can be found here [1]. It doesn't make any mention of the NUL byte, but having worked with it for a while (though admittedly only on the client side), I doubt they would allow NUL bytes within the protocol.


Well they actually say:

ATCP spec said:


IAC SB ATCP	255 250 200	Start sequence
IAC SE	255 240	Stop sequence


The text between the two markers includes the entire ATCP message body, and can be any text you desire (except, of course, the end marker sequence).


I take "any text you desire" as including 0x00, as I might desire that. Interestingly you appear to be able put in anything "except IAC SE" which is a strange restriction. Perhaps they should have said "any sequence at all, provided you convert IAC to IAC IAC" which would let you put in IAC SE by doubling the IAC.

It is funny he doesn't mention not putting in IAC on its own, as if you did that, like this:


IAC SB ATCP blah IAC blah IAC SE


That actually follows his spec - I have put in "any text I desire" and that text does not include "the end marker sequence".

It's tricky to get these specs right, isn't it?
Australia Forum Administrator #69
David Haley said:


What about:
tt="foo";hp=(function(x) while true do x = x end end)(2)

oh no!

Basically, I think you've left the definition for legal values too open by saying it's just any assignable expression in Lua.


Yes, well I was waiting for that one. ;)

I suppose if the server coder wants to annoy the players he has lots of ways of doing it, for example, simple insults. Or, sending down 2 Gb of data between IAC SB x and IAC SE.

At least by setting up a separate environment I am stopping them doing "os.remove "mushclient.exe".

However I haven't effectively dealt with: "repeat until false". I suppose you could build in a runaway instruction timer, but really, I think this won't happen in practice.

It's a trade-off between paranoia about security (like your example of the loop), and ease of use. In the examples I did, you can easily specify stuff like player hit points at the server end, and easily parse them at the client end.

USA #70
Nick Gammon said:


IAC SB ATCP blah IAC blah IAC SE


That actually follows his spec - I have put in "any text I desire" and that text does not include "the end marker sequence".

It's tricky to get these specs right, isn't it?


Yes, but it's implied that you're also following the telnet spec, given most of the preamble of the ATCP spec.
USA #71
Quote:
It's a trade-off between paranoia about security (like your example of the loop), and ease of use. In the examples I did, you can easily specify stuff like player hit points at the server end, and easily parse them at the client end.

But the point is that these things are no longer well-defined, and that it becomes far more complex to evaluate arbitrary expressions -- even if you assume that the server isn't trying to be malicious.
The other example (tt="foo";hp=(function(x) x * 7 end)(2)), while contrived, isn't malicious and could be extended to something more plausible. And maybe you actually want to evaluate the script in some well-known environment (say, previously specified variables).
Regardless, any Lua expression beyond simple primitives and tables of simple primitives will be very hard for other languages to deal with.

My point is just that if you want ease of use, especially across languages, you might want to further constrain what can appear inside an expression.

I think it would be reasonable to give a recursive definition of the types that may be passed:
A value may be of type T, where T is one of:
- a string
- a number
- a boolean
- 'nil'
- a table whose keys are all one of the types above, and whose values are any valid type given for 'T'.

This means that you can syntactically throw out anything with functions in it. To be honest I'm not sure why you would send a function to the user if you're just trying to pass data (don't send a function and tell them to evaluate it: just send them the value).
It could be interesting to send them Lua function definitions that they're supposed to use themselves, but that seems outside the scope of this project.
Australia Forum Administrator #72
Well if you are implying that, then I *can* put in IAC SE as long as I put it in as IAC IAC SE, and that restriction can go away.

If I seem a bit touchy about it, it is because these vague specs tend to be our unbringing [1]. Especially when the ZMP spec specifically uses the 0x00 byte as a string terminator (and then says you can't use that in messages) but you "doubt" that ATCP will support them, even after reading that you can use "any text I desire".

Then we MUD client developers get sledged for "not following the spec" when the specs are loosely and vaguely worded. This reminds me a little of the MXP spec, which was fairly clear about what to do if the input followed the spec, but less clear about what to do if you happened to get something that didn't follow the spec.


[1] Warning, word not in dictionary. :P
Australia Forum Administrator #73
I'll word my earlier question a bit differently ...

Do you think it would matter if MUSHclient agreed to negotiate ZMP, ATCP, MCP (or whatever) through an automatic telnet subnegotiation (ie. it replies WILL to the DO), even if any data that was subsequently sent was ignored?

In other words, is the ATCP, or ZMP stuff supplemental? Or, once you agree to accept it, the server relies on you doing stuff with it?
USA #74
Yes, if you escape IAC into IAC IAC, there's no reason why you couldn't "embed" an IAC SE sequence into the message content.

Good point about NUL, I suppose it is legal. I suppose what I was really thinking is that Iron Realms probably wouldn't ever use NUL in its content, but that's not quite the same as illegal. My brain apologizes! >_> But ATCP is really more of a proprietary protocol - I've only ever seen it used in IRE MUDs.
USA #75
I think that ATCP is more or less fine with being ignored, but ZMP requires that the 'core' package be implemented. I don't know how well it would work to ignore it. This is another reason why I prefer to modularize the protocol handling: each protocol module can handle its own nuances.
Australia Forum Administrator #76
Well, I can think of a whole lot of ways this can fail, unfortunately.

Say MUSHclient automagically agrees to the protocol (it just says "yes" regardless).

Then the server sees "I support MCP/ZMP/ATCP" or whatever and sends things down, expecting the client to render them, or show them, or something, and the player wonders why his room descriptions, or battle information don't appear any more.

BTW this wouldn't apply to my suggested changes on page 1, because what I had was purely supplemental, it simply added to what the player saw anyway.

However if the client waits to be "told" by a plugin that it supports (say) ZMP, then we have a few scenarios:

  • The player connects, the server queries, gets told "no", and *then* the player installs the plugin. However the plugin doesn't work because the negotiation is already over.
  • The player installs the plugin first, then connects, and all works OK, until the player removes the plugin. Now the server is committed to sending the extra stuff, but the client now ignores it, as the plugin is gone.
  • The plugin developer keeps re-installing the plugin, causing the plugin to be starting from scratch, but the server thinks it has been there for a while.


How do you solve that now, with the ATCP plugins? Or do they only work properly if you have them installed before connecting?
USA #77
Nick Gammon said:
*The player connects, the server queries, gets told "no", and *then* the player installs the plugin. However the plugin doesn't work because the negotiation is already over.

We already deal with this now; it's an inherent feature of the protocols I know of that the query sent by the server is sent on connect. Simply ensure that the protocol plugin is installed beforehand. Out of all the problems ATCP-users have had during troubleshooting, this was not one of them.

Nick Gammon said:
*The player installs the plugin first, then connects, and all works OK, until the player removes the plugin. Now the server is committed to sending the extra stuff, but the client now ignores it, as the plugin is gone.

There's not much you can do about that anyways, unless you mark a plugin as a 'protocol' plugin somehow and prevent users from uninstalling it. My webclient idea would have made protocol plugins entirely separate components in the first place, which I think nicely deals with that, however we don't really have that option here. I don't think it's too much to ask that users not disable the plugin, just like they have to make sure it's enabled before connecting.

Nick Gammon said:
*The plugin developer keeps re-installing the plugin, causing the plugin to be starting from scratch, but the server thinks it has been there for a while.

Ctrl+Shift+K, Ctrl+K: disco/reco. It can be a little annoying, but it's rather difficult to avoid (it's something that affects more than protocols in general), and once you get used to it, it becomes second nature.


Nick Gammon said:
How do you solve that now, with the ATCP plugins? Or do they only work properly if you have them installed before connecting?

Generally speaking, the ATCP-using plugins can generally be developed like any other plugin; the disco/reco trick is only necessary when the changes affect protocol-relevant details. With my newer versions of the ATCP plugins (not yet released, I'm hoping to see PPI through first since they rely on it), the ATCP plugin handles all protocol details, and the other plugins just tell it what they want to listen for. I achieve this by enabling all ATCP packages straight off, and just forwarding the received messages to any listeners. It's safe to do this because ATCP doesn't generally require responses or expect certain behavior: it's largely used as an event-push protocol.

I would imagine that ZMP would require a slightly different approach due to the nature of the packages it exposes. It's likely that a ZMP plugin would itself only implement the core package, and leave other plugins to implement other packages. I suppose it would be something of a "ZMP plugin suite", a set of plugins in a "structured .plugin folder" like I laid out in a topic here recently, with one primary plugin that enables/disables the other plugins on demand on connection. That would allow maximum extensibility in the face of new packages.
Amended on Thu 04 Feb 2010 05:33 AM by Twisol
Netherlands #78
Okay, I don't look for a few hours and there's another page. Let me go catch up.

Telnet negotiation details:
Yeah, this is a pain. The way I solved it with my mudserver was somewhat easier seeing how there were less issues, but the principle is the same. MUSHclient basically needs to remember the negotiation state for the 255 protocols, and track seperately whether it is supporting it for the outgoing data or the incoming one. The biggest problem is abstracting away the protocol details to a level of 'do I want to play ball on Xxx', so scripters do not need to worry about the precise details of when and where to apply/deny. I call them server_offer() and server_request() I believe, and I've got a client_reply() and client_request() as well since sadly, the telnet protocol is a confusing thing in how it is worded and in my experience, each protocol interprets the side with the initiative slightly differently, so simply waiting isn't always an option, nor is always offering or declining in advance.

I think the cleanest way to solve the problem with different protocols and different plugins 'fighting' over the same protocol at is to have the handlers for (sub)negotiation return a boolean 'exclusive'. False basically means you are just listening and not taking actions, true on the other hand means you are taking actions (sending stuff, changing some system state, etc) that would conflict with other plugins. If two plugins both return true, you know there is an incompatibility inbetween plugins. Of course, it depends on plugin authors playing nice and not returning false no matter what they do.

But I haven't fully thought this through yet, so it needs a lot of refining still.

ATCP details:
Always negotiating anything that is offered is a bad thing. ATCP has a seperate layer of authentication, and I know for a fact that the muds in question like to give a spammy shitstorm if you enable it but don't authenticate properly. Apparently changing the authentication logic is how they filter out outdated clients, which leads to five lines of spam every 10 minutes. (Or something like that, don't recall the precise details.)

Thankfully, ATCP in its current usage is pretty stateless. I basically assume it is off until I receive some data, or until I connect and negotiate it. There is no data to lose since the authentication is really a one-time thing.

Oh, and I have (using Ethereal five years back) seen references to file uploading and downloading facilities, likely for their webbased clients, but I think it would also accept custom settings. Either way, I do expect NULL bytes in there.
USA #79
Worstje: In regards to ATCP authentication, it's interesting to note that Achaea no longer requires it in general. I attempted to use my plugin on a non-Achaea server and it didn't work, though.

As for the uploads/downloads thing, I do know that IRE's Nexus client uses ATCP to transmit the user's settings back and forth. However, it is base64-encoded. (Hooray for Wireshark)
USA #80
I don't view it as such a bad thing to require the plugin to be installed prior to connection, and to require that the plugin not be uninstalled during connection (or weird things will happen). It's comparable to expecting hardware to work before you have the driver installed, or keeping the hardware plugged in and somehow removing the driver and expecting the hardware to keep working.

I think somebody (Twisol?) mentioned a flag that you could mark plugins with to prevent them from being uninstalled during connection, and that would address the latter problem above, but frankly I don't think we need even that. I'm willing to tell users of a plugin "hey, you did something dumb, just restart your connection".
USA #81
David Haley said:
I think somebody (Twisol?) mentioned a flag that you could mark plugins with to prevent them from being uninstalled during connection, and that would address the latter problem above, but frankly I don't think we need even that. I'm willing to tell users of a plugin "hey, you did something dumb, just restart your connection".

The flag was more of a thought experiment than anything, I don't think it would work terribly well anyways.
Australia Forum Administrator #82
Right then. My current implementation seems OK, it is now being tested. Being a rewrite of subnegotiation, everything that used to use it is now possibly broken. For example: MCCP v1, MCCP v2, IAC GA, IAC EOR, IAC IAC sequences, MXP, CHARSET negotiation, NAWS, Terminal type.

As suggested by Worstje, "unknown" negotiation types are now handled by a plugin callback, eg.


function OnPluginTelnetSubnegotiation (type, data)
  Note ("received negotiation: ", type)
  Note ("Received option string ", utils.tohex (data))
end -- function OnPluginTelnetSubnegotiation


So, for example, ATCP stuff would appear here with type 200 and the data in the "data" variable. Unfortunately ZMP with its zero-terminated strings will need special processing.

I am thinking next of allowing the client to respond to the server IAC DO x message by doing another plugin callback (to save mucking around with packet processing). eg.


function OnPluginTelnetRequest (type, data)
  Note ("received negotiation: ", type)
  Note ("Received option string ", data)

  if type == 200 and data == "WILL" then
    return true
  else
    return false
  end -- if
end -- function OnPluginTelnetRequest


This lets a plugin inform the server that it is interested in subnegotiation packets for that particular type.

This low-level plugin callback basically lets you implement any protocol you like (you could, for example, ignore my earlier suggestions). However it should simplify your ATCP plugins.
USA #83
I'm confused, are we talking about the Lua protocol thingy or something else entirely at this point?
Netherlands #84
@Nick:

Awesome. Again pretty much the way I suggested. (The OnPluginTelnetRequest bit, that is.)

What about world functions like GetTelnetOptionInfo(opt, telnet.negotiatedclient) and other bits (or negotiated serverside) of info regarding telnet negotiation a plugin author might want to know without being in a plugin callback itself?

Also... Lua supports \0 bytes, so the only thing you need to do is figure out how to properly send over a 'pascal' style string to the WSH engine, correct?

@David Haley:

The discussion is now on the framework that allows plugin authors to implement any form of telnet protocol themselves. Basically the framework that would make the LNL or ZMP implementable.
Amended on Thu 04 Feb 2010 08:32 PM by Worstje
Australia Forum Administrator #85
In the case of:


IAC SB x <data> IAC SE


You will receive <data> (and x). What you do with it is up to you. This is consistent with my Lua suggestion, as you could process the data through the Lua interpreter. Or not.
Netherlands #86
Oh, and while we are on the topic of the plugin callbacks. Could it be a reasonable thing to request the help page for plugin callbacks to get split up in different pages, with the index having each different plugin callback listed. There is no order to it, and finding a specific callback has been getting progressively more difficult with each added callback in the last few years.
USA #87
I guess I should have been more precise in my question; a component of my question was: had we "finished" the "LNL" protocol discussion?
Netherlands #88
I think we discussed as much as we could possibly discuss. Lasher isn't taking part in the discussion, and he'd pretty much be the key part of any implementation.
Australia Forum Administrator #89
Worstje said:

Also... Lua supports \0 bytes, so the only thing you need to do is figure out how to properly send over a 'pascal' style string to the WSH engine, correct?


Yes, and it doesn't seem to be working. :(

After a bit of tweaking of data types it is successfully passing the string with an imbedded \0 into the Lua engine. However there doesn't seem to be a variant type (correct me if I am wrong anyone) that supports null-terminated strings, and variants are what the WSH engine uses.

I got to the stage of setting the string data type for the parameters to the WSH call, and carefully passed the string *and* the length to it, ending up (using the debugger) here:


const COleVariant& COleVariant::operator=(const CString& strSrc)
{
	USES_CONVERSION;
	// Free up previous VARIANT
	Clear();

	vt = VT_BSTR;
	bstrVal = ::SysAllocString(T2COLE(strSrc));
	if (bstrVal == NULL)
		AfxThrowMemoryException();

	return *this;
}


Now the CString has the correct length inside it. But when creating the BSTR value it uses SysAllocString and the documentation for that says:


Parameter

sz

A zero-terminated string to copy. The sz parameter must be a Unicode string in 32-bit applications, and an ANSI string in 16-bit applications.


So it doesn't look possible. Hey, and this is why you don't use 0x00 as an internal delimiter when defining a protocol. :P

I can work around it, whilst I still have the string with the nulls, break it up myself at the nulls, create a table, and call the callback with that table.
USA #90
Nick Gammon said:
Hey, and this is why you don't use 0x00 as an internal delimiter when defining a protocol. :P

No, this is why you don't allow NUL bytes as content. Using NUL as a delimiter is fine, just split by NUL and pass the resultant list.

EDIT: If you take it upon yourself to always split protocol entries by NUL, it might cause issues with other protocols that do allow NUL for one reason or another. This is another of those nuances that I would prefer to be handled by the protocol plugin itself, and I don't think it's unreasonable to suggest that if a protocol uses NUL bytes, you're better off using Lua. The Lua plugin can always make the content available to other plugins, either through broadcasts or through registration-over-PPI.
Amended on Thu 04 Feb 2010 09:44 PM by Twisol
Australia Forum Administrator #91
Twisol said:

No, this is why you don't allow NUL bytes as content. Using NUL as a delimiter is fine, just split by NUL and pass the resultant list.

EDIT: If you take it upon yourself to always split protocol entries by NUL, it might cause issues with other protocols that do allow NUL for one reason or another.


So it's fine, but it isn't fine, is that it?

OK, that's it. I just don't have the energy to make another plugin callback calling routine, just to handle one case.

I'll go with Twisol's suggestion. If you want to handle ZMP, you have to use Lua, or make a Lua intermediate plugin to break up the parameters for you.

And I just suggest to the ZMP author, whilst using 0x00 delimiters might be defensible on all sorts of technical grounds, it just is another barrier to implementors, given that the C libraries (and other things like data types in the scripting engines) don't like imbedded 0x00 in strings. Sure, it can be worked around, but it just makes the job harder.
Amended on Thu 04 Feb 2010 10:01 PM by Nick Gammon
USA #92
Nick Gammon said:
So it's fine, but it isn't fine, is that it?

Heh, it's "better" than using nulls as content. Using NUL as part of the protocol itself means it's easier to deal with earlier in the process. Putting them in the actual content creates migraines.
Australia Forum Administrator #93
Well, for MUSHclient, to use it for a specific purpose like breaking up parameters means I need to "teach" the client the exact protocol at the low level. However as you noted, breaking up at 0x00 bytes (into a table, say) is very specific to ZMP. If ZMP had chosen any other character as the delimiter (eg. 0x01 which doesn't normally occur in text) then the whole subnegotiation text would be the content, it could be passed to a plugin, and that plugin could say "hey, I'll break it up at the delimiter as I happen to know that is how ZMP works".
Australia Forum Administrator #94
Even now it works with Lua, so the basics inside MUSHclient are fine. It just limits its use with any language that happens to use null-terminated strings internally to hold "content".
USA #95
Quote:
I think we discussed as much as we could possibly discuss. Lasher isn't taking part in the discussion, and he'd pretty much be the key part of any implementation.

Are we developing something for Lasher or as a proposed reusable community protocol?

I don't really care what Lasher ends up wanting to do with Aardwolf, he can do whatever he like and if Nick supports it that's great for Aardwolf. But the nominal topic here is to suggest a standard of some sort, which -- as I understand things -- means that it's not just an Aardwolf feature.

So, I'm just trying to get context here...



A practical concern that has not yet been addressed is what kind of limit is imposed on the Lua expressions, because (as it stands now) some expressions can actually be very difficult to parse in other languages, even without going to actively malicious expressions. A protocol that aims to be simple and language neutral, and yet has this issue, has failed one of its stated design goals.
Australia Forum Administrator #96
The topic has really broken into two pieces as far as I am concerned.

Graceful handling of telnet negotiation


This developed after discussion about how a new protocol wasn't really needed, but we did need:

  • A way to easily capture telnet subnegotiations without having to get into delving into incoming packets in a script
  • Removal of the restriction on the length of such content packets (eg. for my proposed system, or any other)
  • Better handling of IAC IAC
  • Fixing some bugs in the current implementation where you could not have imbedded CR/NL/ESC inside telnet negotiations


This goal has been achieved to my satisfaction by adding various new things:

  • Rewritten telnet subnegotiation state machine
  • IAC IAC now allowed inside telnet subnegotiation
  • CR/NL/ESC now allowed inside telnet subnegotiation
  • Telnet subnegotiation can be any length
  • Once completed, the "content" part (the <data> part in IAC SB x <data> IAC SE) is passed to any plugin that wants it, along with x
  • Querying plugins on receiving IAC DO x and IAC WILL x to see if they want to handle it, and if so, replying IAC WILL x or IAC DO x.
  • Support for any telnet protocols (including ones I haven't heard of) by sending all unknown subnegotiation messages to the above plugins. Known ones continue to be handled in the existing way (eg. MCCP).


This effectively supports all protocols, both existing and future, by providing a framework to get at the content (the part inside the telnet subnegotiation) without needing to get to the packet level in plugins.

Effectively, it is irrelevant whether the content is Lua, ATCP, the Aardwolf protocol (which at present is not Lua, but simply various settings), ZMP or whatever.


My proposed protocol for server -> client


My suggestion there stands, although now it would be easier to use any other telnet type (than 102) because the new MUSHclient handler would let you choose 103, 104 or whatever. It will also work more reliably (you don't need to worry about newlines in the "code").

I take your point about someone sending "while true do end" but as a server writer don't really care about that. I won't send that, any more than I will send 2 Gb of data in one message, or put insulting messages in my MOTD.

I think that if I wanted to write my own client/server pair (for instance, a server where *all* traffic was out-of-band) then my proposed method will work fine. I will control the server, and will write the client plugins. I won't moan to myself that, say, Python isn't supported because I am working closely with myself.

However anyone that wants to press on with ZMP, ATCP or any other protocol is welcome to, and the changes in MUSHclient will make that much easier, and in no way limit their ability to do so.
Amended on Thu 04 Feb 2010 10:42 PM by Nick Gammon
USA #97
I think there are non-contrived reasons why somebody might want to output expressions that involve actual expression evaluation. (You could think of this as similar to "entities" in MXP; you send a definition once in a command, and then reuse it.) Regardless, allowing expression evaluation is not consistent with the idea that this is easy and simple in general. If languages than Lua aren't on the table, that's fine. So this is not a protocol for the server to send "out of band" messages to the client: it's a protocol to do the same using Lua.

Nick Gammon said:
I won't moan to myself that, say, Python isn't supported because I am working closely with myself.

But this isn't really the point, is it? Your comment here makes sense if you're doing something for one MUD. It doesn't make sense if we're trying to develop something that many MUDs can use, potentially with many clients.

This started out sounding like a general protocol, that other clients could pick up, and that people could handle in many scripting languages.
Now it sounds like a protocol meant for some rather specific server to talk to some rather specific client, using a rather specific plugin language, with a rather specific person (or small group of people) in full control of all steps involved.
This isn't a "bad thing" at all, it's just that it's different. As I said re: Lasher & Aardwolf, I really don't care what people feel like doing when it comes to their own client/server pairs. But I do care what happens if we're trying to develop a reusable standard.
Australia Forum Administrator #98
David Haley said:

This started out sounding like a general protocol, that other clients could pick up, and that people could handle in many scripting languages.


My original post on page 1 was really trying to suggest a method (not propose a standard that everyone should agree on), but a method that a server could use to simply send extra data (like your hit points) to a client, so that client, any client, might display that in a bar, window, speak it out loud or whatever.

Now my suggestion was to use Lua simply because some mainstream clients I know of already implement Lua (MUSHclient, cMUD, Mudlet), so interpreting the data would be simple for them.

Other clients that don't have Lua incorporated could, if they are still being maintained, add it. But if they don't no big deal. However I should point out that any alternative, such as JSON, or XML, or the ATCP, or MCP protocols are not automatically there in every client anyway. So if your objection is "they would have to add Lua to make it work", I would reply, "so what? if you go with JSON then then have to add the JSON libraries, what is the difference?", or XML, or stuff to handle ATCP.

My question is, if you have a server that is based on Smaug, and you incorporated my suggested C code into it, would harm would it do? Your players could then install the health bar plugin if they happened to use MUSHclient. If not, the server would see the initial negotiation failed, and not do any extra work. If so, they get a better experience. Then someone who uses cMUD might do a cMUD plugin as well. Ditto for Mudlet. Then other clients might incorporate Lua.

I don't quite see what the objection is.

Netherlands #99
Nick Gammon said:

... But when creating the BSTR value it uses SysAllocString and the documentation for that says:


Parameter

sz

A zero-terminated string to copy. The sz parameter must be a Unicode string in 32-bit applications, and an ANSI string in 16-bit applications.


So it doesn't look possible. Hey, and this is why you don't use 0x00 as an internal delimiter when defining a protocol. :P

I can work around it, whilst I still have the string with the nulls, break it up myself at the nulls, create a table, and call the callback with that table.


Why go through all that effort? I raise you SysAllocStringLen (http://msdn.microsoft.com/en-us/library/ms221639.aspx):

Quote:
BSTR SysAllocStringLen(
const OLECHAR *pch,
unsigned int cch
);

Parameters

pch

A pointer to cch characters to be copied. If pch is NULL, a new string is allocated that contains only the null character.

cch

The number of characters to be copied from pch. A null character is placed afterwards, allocating a total of cch plus one characters.


If you are copying non-NULL-terminated buffers into a new non-NULL-terminated buffer, don't use a NULL-terminated buffer creation method. :)
Amended on Thu 04 Feb 2010 11:18 PM by Worstje
Australia Forum Administrator #100
The changes to the code have been pushed to GitHub, if you want to check them out:

http://github.com/nickgammon/mushclient

They are not fully tested yet, but you can see the scope of the changes to date.

Australia Forum Administrator #101
Worstje said:

If you are copying non-NULL-terminated buffers into a new non-NULL-terminated buffer, don't use a NULL-terminated buffer creation method. :)


Well I didn't do it deliberately, that was all in the MFC libraries. However thanks to your pointer I have adapted your idea and it seems to work.
USA #102
Quote:
So if your objection is "they would have to add Lua to make it work", I would reply, "so what? if you go with JSON then then have to add the JSON libraries, what is the difference?", or XML, or stuff to handle ATCP.

There are well-known libraries for handling e.g. JSON or XML in many libraries. To handle "LNL" as you have defined it so far, one must fully embed Lua.

This issue goes away, incidentally, if you use the relatively simple legal expression definition I gave earlier.

Quote:
My question is, if you have a server that is based on Smaug, and you incorporated my suggested C code into it, would harm would it do? Your players could then install the health bar plugin if they happened to use MUSHclient. If not, the server would see the initial negotiation failed, and not do any extra work. If so, they get a better experience. Then someone who uses cMUD might do a cMUD plugin as well. Ditto for Mudlet. Then other clients might incorporate Lua.

It depends on whether you're trying to achieve standardization by design or by accident. MXP attempted to achieve standardization by design but mostly failed due to issues both with the spec and with "famous" implementations of the spec. It sounds like you don't care about standardization, but if other people use it, that's great too.

As a server author, I want it to be easy for players to use this thing I'm going to spend time working on. Prompt data is really only a fairly trivial application of out-of-band data. Invariably, more complex information will be sent (as you are already sending with Aardwolf).
Therefore, to maximize utility for the time I spend, I want to make sure it's pretty easy to use this stuff in other clients as well. If all the other clients already support Lua as a plugin language, that makes life easy -- although then I have to assume that the clients have cross-plugin communication, or I force people to write associated plugins in Lua as well. If clients don't already support Lua, then life gets pretty complicated all of a sudden. Either I fully support the spec and allow full Lua expression evaluation, or I go against the spec and only allow primitives (for example) which then creates confusion because the spec is no longer trustworthy as an indication of what implementations support.

I just don't want to see something that has potential to be great end up falling to the wayside due to issues that we've seen with standardization in the past. We are in a fairly unique situation here of being able to drive a standard, assuming that MUSHclient implements whatever standard we end up deciding upon -- if indeed we're even trying to do that.
Netherlands #103
@ Nick Gammon:

Yeah I understood that much. I specified the logic (although I'll declare guilty at phrasing it a bit smart-alec-like) to point out my reasoning and how I found the replacement function. I knew neither function up till just now. :)

@ David Haley:

I completely agree with your concerns. It is why I put forward the minimized table syntax minus expressions bit a few pages ago.

Of course, personally I am still fond of bencoding. It is written to have very little overhead (not all servers use MCCP), and assuming we'll get out of band data every prompt, I think saving on the amount of characters transmitted is still a quite important endeavour. People might not be using old-fashioned 56k telephone modems where I live anymore, but I know plenty of people in the USA and other countries do still.
Australia Forum Administrator #104
Well I tested my new modifications on Achaea with reasonable results.

Armed with this simple plugin:


<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE muclient>

<muclient>
<plugin
   name="Achaea_Test"
   author="Nick Gammon"
   id="3d13120b92f9f93af9ac6f42"
   language="Lua"
   purpose="Tests Achaea ATCP"
   date_written="2010-02-05"
   requires="4.48"
   version="1.0"
   >

</plugin>

<!--  Script  -->

<script>
<![CDATA[

function OnPluginTelnetSubnegotiation (type, option)
  AppendToNotepad ("Achaea", option .. "\r\n")
end -- function OnPluginTelnetSubnegotiation

function OnPluginTelnetRequest (type, data)
  if type == 200 and data == "WILL" then
    return true
  end -- if
end -- function OnPluginTelnetRequest

]]>
</script>

</muclient>


I got the following results (in part):


Room.Num 23214
Room.Brief Inside the World Tree
Room.Exits e
Room.Num 23214
Room.Brief Inside the World Tree
Room.Exits e
Room.Num 21410
Room.Brief A dimly lit cave
Room.Exits o
Room.Num 21410
Room.Brief A dimly lit cave
Room.Exits o
Room.Num 22698
Room.Brief Upon a flower-laden hill
Room.Exits ne,nw,u,d,i
Room.Num 23812
Room.Brief Path hugging the side of a hill (road)
Room.Exits n,sw
Room.Num 23188
Room.Brief Mud-covered trail (road)
Room.Exits e,s
Room.Num 21494
Room.Brief Beneath a large oak tree on the hillside
Room.Exits w
Room.Num 23188
Room.Brief Mud-covered trail (road)
Room.Exits e,s
Room.Num 21494
Room.Brief Beneath a large oak tree on the hillside
Room.Exits w
Room.Num 23188
Client.GoodBye Farewell, adventurer.


So that is a lot simpler than mucking around grabbing stuff out of packets. :)
Amended on Fri 05 Feb 2010 03:46 AM by Nick Gammon
USA #105
Nick Gammon said:
So that is a lot simpler than mucking around grabbing stuff out of packets. :)


Yeah, really. O_o I've been mucking around with writing a telnet state machine to filter out ATCP data. (I'm still working out the kinks XD)

I assume multiple plugins can implement these callbacks without clashing?
Australia Forum Administrator #106
Yes indeed. Any number of plugins could get the callback.

In fact, it could be a lot simpler for each plugin to just parse the ATCP lines, which so far look really simple, than have some sort of "master" module do that, and pass that around to the others. A simple Lua regexp would do that.

Then each plugin could stand alone (which is really what I was suggesting in page 1 of this thread) so you add and remove them as you want, without needing to ensure that some "central" plugin was there as well.
USA #107
Yeah, that makes a lot of sense. I think I will keep working on the core ATCP plugin, though, because some of the features I'm adding will be very useful. For example, I'm seeking the IAC GA sequence at the end of the Achaea prompt, and shipping the states of the current ATCP messages to registered plugins. This is useful because a plugin might (and often does) want multiple pieces of data before it can do something.

I'm not sure how easy it would be to integrate that IAC_GA-sensing technique with the new plugin callbacks you added, since I don't see any way to notice GA after all the ATCP messages are received except through OnPluginPacketReceived. And I would imagine that OnPluginPacketReceived is called -before- the new callbacks, since the new callbacks represent a certain amount of packet processing.
USA #108
I was reading over the v4.48 changelist, and I noticed this line:

Quote:
In the case of type 102, then OnPluginTelnetOption is called instead (without the option number, as that is fixed at 102). This is for backwards compatibility

Why "instead"? Why not use OnPluginTelnetSubnegotiation if available, and fall back to OnPluginTelnetOption otherwise?
Australia Forum Administrator #109
I'll just call them both, that is simpler than having stuff like "call one script if the other doesn't exist".

That way new plugins can just use OnPluginTelnetSubnegotiation for all message types.
Australia Forum Administrator #110
For sake of completeness in documentation, I have decided to treat the sequence:


IAC SB n <some data> IAC x


... where x is neither IAC or SE as if x is SE.

In other words;

  • The subnegotiation sequence, although incorrectly terminated, is processed as if it were correctly terminated.
  • The sequence IAC x does not start a new command sequence.
  • The character x does not form part of the input stream.


I believe this treatment:

  • Is not inconsistent with all the existing specs I have seen - that is, I have not seen an official document that suggests that it be done differently.
  • Discourages server writers from trying to "save a byte" by omitting the SE.
  • Discourages server writers from trying to save two bytes by switching from one sequence to another, as they might be tempted to do, if you treated it as:

    
    IAC SB n <data> IAC SE IAC x
    

  • Keeps the state machine simple.

Australia Forum Administrator #111
Version 4.48 has been released now, so if you ATCP experts want to try it out, I would be grateful.

You should be able to use a plugin callback similar to the one earlier on the page to easily extract out the ATCP data.
Australia Forum Administrator #112
David Haley said:

I'll fess up straight away that I don't really see the advantage of this over, say, ZMP. I don't know ATCP but apparently it does something kinda similar. But really, this kind of protocol exists, and ZMP at least is quite simple, and it's easier to define semantics in ZMP using the package/command mechanism. (ZMP is also very easy to parse.)


I've tried to find a MUD that implements ZMP, without a heap of success. Some of the pages that are supposed to be about ZMP are in fact just advertising:

http://www.awemud.net/zmp/draft.php

A claimed implementation, TeensyMud, doesn't seem to exist, or be up.

If I could find a ZMP MUD I could perhaps check that MUSHclient can parse the protocol - at least at the low level, the part that has the 0x00 bytes in it. So if you could advise me of an active one, that would be great.

But if not, your original question is:

"Why did you develop your own protocol and not use <a protocol that is much better but that absolutely no-one uses>?"

And I would reply, "well, why does no-one use this better protocol then?".
USA #113
It's hard to get people to adopt a fundamentally client-driven protocol when you're not in the business of writing clients. ;)
For instance, MXP would have had far less success -- even considering the relatively small success it has now -- had it not been written and pushed by somebody who happened to be the author of one of the major clients.

Client authors get to set standards, much like how web browser authors get to set (mis)standards. That's why I'm pushing the strong, standardized spec here, because whatever ends up happening is likely to become a standard to some extent, by accident or by design.



As to MUDs that run ZMP, you'd have to ask Elanthis. Supposedly he has a program called 'SourceMUD' that runs ZMP. And there were some mentions in the threads on MB, but I don't know if any of them became public releases.
Amended on Sat 06 Feb 2010 03:33 AM by David Haley
USA #114
I just remembered one big, major reason why the ATCP core plugin is still important: the protocol requires a single 'hello' message to be sent before anything else, listing the packages that you want to be enabled. Single plugins could do it on their own, but only the first hello message the server receives is taken into account.
USA #115
I'm modifying a copy of my ATCP plugin to see if I can work the new callbacks into it, and I notice right away that I have a problem with the hello message. By returning true from OnPluginTelnetRequest, MUSHclient sends IAC DO ATCP, but I need to send the hello message immediately after IAC DO ATCP. Sending the hello message before IAC DO ATCP is very unlikely to work properly, but I can't do it later (after the function returns) either, because I have absolutely no guarantee that nothing else will have been sent by then.
Amended on Sat 06 Feb 2010 10:09 PM by Twisol
Australia Forum Administrator #116
In the OnPluginTelnetRequest send IAC DO ATCP and then the "hello" message. That way you guarantee it directly follows the "DO". The client then sends a second DO which hopefully won't cause a problem. If it does, try returning false, hopefully the server won't be confused by DO followed by DONT. If that doesn't work, get back to me.
USA #117
Yeah, that definitely didn't work. It threw the client into an infinite loop, it probably flooded Achaea with lots of alternating IAC DO ATCP and IAC DONT ATCP too.

EDIT: Well, an -effective- infinite loop. It wasn't looping forever in the same area of code, but it was doing the same thing over and over again because Achaea kept sending IAC WILL ATCP. At least that's my guess. I wish I had Wireshark running at the time, but I'm not about to unleash that on Achaea again. XD I only know it wasn't a typical infinite loop because the client was still slightly responsive, for example when I killed it through the Task Manager, it asked if I wanted to save my world.
Amended on Sun 07 Feb 2010 12:05 AM by Twisol
Australia Forum Administrator #118
Yes, well that is tricky to get right. I was thinking of letting you do the IAC DO 200 (and add your hello message) but potentially you might have lots of plugins that then all agree to the protocol, and cause the sort of loop you described. After all, you don't want two "hello" messages, nor do you want zero of them.

I have changed it in 4.49 to do two extra things:

1. Disable negotiation loops, by not processing the WILL or WONT message more than once.


2. After you *agree* to the WILL, your function OnPluginTelnetRequest gets called a second time with "SENT_DO". This lets you do the "hello" message once, at the correct time.
Australia Forum Administrator #119
And also, it now only calls OnPluginTelnetRequest until it gets a positive response. This is so you don't login twice, if you have multiple plugins that might be interested in that protocol.

This lot of fixes is now pushed to GitHub if you want to experiment with a private build.
Australia Forum Administrator #120
I spotted a problem with the negotiation, if you have multiple plugins it was previously assuming that, if you didn't have any OnPluginTelnetRequest callback, that it DID have one, and it returned true.

This would really throw out negotiation, as the first plugin on its list might not have the OnPluginTelnetRequest callback, it responds true, but doesn't actually do anything.

Try the latest GitHub version, or wait for version 4.49 to fix this.
Australia Forum Administrator #121
Version 4.49 is now released, in case anyone who does telnet negotiation plugins would like to try it.
USA #122
How does t.combat work? I am putting my victim hp bar into an envy codebase and it's not knowing when I'm in combat. When I do the debug, I get a proper victim name, victim level, victim hp, and victim maxhp, but it doesn't show up on victim bar because of my ifcheck for t.combat.

EDIT______________

Nevermind, I think I figured it out. I took out the combat check in show_status. Put it back in and going to play with it a bit.
Amended on Sun 14 Feb 2010 02:01 AM by Orik
Australia Forum Administrator #123
In my code:


(ch->fighting && ch->fighting->who) ? "true" : "false",


... the flag was true if the character was marked as fighting, and fighting a non-null victim.

If this is always false you may need to look at where that boolean is set up.
#124
The real might in this protocol is the way the server tells the client to generate a data structure. I am rather enthusiastic about this.

But I think you are not pushing the possibilities this allows us (us being MUD-admins and the client developer) far enough.

As you know, I am campaigning for a better world, where a MUD-player only has to install the client, enter his server-address and play. Now, this protocol allows us for the first time to present any player in _any_ MUD a generic GUI that is automatically adapted to that MUD. Without needing the player to perform additional configuration/installation.

How can this be achieved? Well, you're plugins are nearly there. You'd need to:

1.) Modify some of your more generalizable aardwolf plugins,like the health-bar, not to display the contents of 'tt.hp' and 'tt.maxhp' but for example the contents of the sub-elements in: 'tt.bar.' and 'tt.maxbar' (like 'tt.bar.hp' and 'tt.maxbar.hp' or it could be 'tt.bar.beer', 'tt.maxbar.beer' ...). The functionality stay the same, but the health-bar plugin adapts to the contents the MUD-server is sending!

2.) Package this (and maybe other) generalized plugins with the installation of mushclient! This is important. Your protocol is great for this because it is downwards and upwards-compatible. We will not have compatibility issues.

From this we gain:
- the user can have a "wow! a health bar!" reaction directly after installing Mushclient.
- I can tell my users to upgrade their old Mushclient versions and they will immediately see the mini-windows and health-bar without them changing a thing in the client.
- we reduce the workload of MUD-admins and invent the wheel only once for the whole community. Mud-admins needn't write plugins any more, just send the data they want over the protocol channels. The plugin was already written: One health-bar-plugin to rule them all.
- the plugins will be automatically updated with every upgrade of the client.


In effect I am able to present and change a GUI to all my players without them needing to do anything.


Please seriously consider this (packaging the plugins with the install / making the health-bar plugin adaptable), as I see much potential with it. I will gladly work to help you test and implement this.

Xtian

ps: To the other readers: Please don't forget that most readers here are Mushclient-Fanboys and as such experienced MUD-players, and probably consider themselves techies. Most of my players, on the other hand, don't even try to stay up to date with new Mushclient versions and many find it difficult to search for and install an additional plugin by hand. They're that kind of users.
Australia Forum Administrator #125
Thank you for your enthusiastic comments. Until now most of the replies on this thread had been about the flaws in my system, without much appreciation of the benefits.

I see what you mean about the generalized bar, I might do a bit of work in that direction. For example recently I had been adapting the health bar that works from ordinary prompts to show "hp", "endurance", "guile" rather than "hp", "mana", "move".

Your suggestion I gather is to send something like:


t.bar.HP = 40; t.maxbar.HP = 100; 
t.bar.Move = 10; t.maxbar.Move = 50;
t.bar.Guile = 500; t.maxbar.Guile = 600;


... and so on, so that the bar automatically displays (in this case "HP", "Move" and "Guile". And if you added a fourth item the box would just get bigger.

To make this work generically we would have to agree on some keywords, so that the plugins would work with any generic MUD (in this case, the keywords bar and maxbar).

For example:


t.combat = true;   -- or false obviously
t.dead = true;
t.status.blind = true;    -- status afflictions (debuffs)
t.status.stunned = true; 


Combat info might be usefully represented as a separate status box (much like in WoW) which might only appear if you are in combat. Something like:


t.target.name = "Large Naga"; 
t.target.percent.HP = 40;
t.target.percent.Mana = 30;


I'll put a bit of work into generating some ideas along these lines, and post more shortly.
USA #126
Just to briefly note, ZMP has a sort of "subwindow" package that hasn't had much use yet, and was designed for something like the MUD-controlled windows youy're suggesting.

Also, I think this protocol is very cool on one level, given that it's simply Lua; the possibilities are endless. Yet on the other, there are some criticisms that should be brought up, for the good of the protocol. If I never got any criticism on my ideas, I'd think something was wrong, because nothing is perfect the first time through.
USA #127
It's a Very Hard Problem (tm) to write a generic data passing protocol such that clients can automagically draw an appropriate GUI. It's hard enough when you have a subwindow package that handles presentation (as opposed to semantics), but far harder when you want to pass semantics instead of presentation. A simple reason is precisely that you need all users of the protocol to agree on what the data mean, which is a very difficult task.

My previous comments were made under the assumption that this was always meant to be a general purpose, generic protocol that could be plugged into servers and clients. I later found out that this was meant to be a custom job of sorts that one server and one client (via plugins, perhaps) would agree upon. That changes things considerably. But if the idea has again become to make a generic protocol, then I think that many of the points brought up are rather legitimate and should be reconsidered.

So basically it's not just a question of passing some data around. That's the easy part, really (and even then it is not clear that the protocol proposed here is the "best" way of passing data around for a generic protocol). It's what comes next that is considerably difficult; I've seen many protocols attempt to and then fail to solve this problem. This is why I am somewhat wary of claims to simplicity. Don't get me wrong, though, of course I am extraordinarily excited to see something like this come about. I just don't think it's quite a simple as has been made out.

For example, to enforce semantics, you will eventually be reimplementing a lot of what ZMP did, perhaps in a different way w.r.t. data separators or whatever, but the initial argument given in favor of this protocol was that it is "simple" (assuming Lua environments on at least the receiving side). That argument very rapidly goes away when you want to start discussing semantics -- even if those semantics are not data semantics, but semantics for calling presentation functions.
Australia Forum Administrator #128
You both make good points. :)

I was excited about what Xtian said, because I think fundamentally he is correct - it would be fabulous if, for small effort, MUDs could have XP bars, gauges, status information (eg. blindness, death) shown more or less as "default" behaviour you get with a standard client.

I am experimenting purely privately right now to see where these ideas may lead us. Already a couple of interesting points have arisen. It is a nice idea to have an automatic health-bar window appear with generic bars like hp, mana, move, guile, rage, endurance and so on, but ...

  • Given that the client is automatically displaying these bars, what order do they appear in? Alphabetic? Random? Server-defined? Alphabetic happens to work quite nicely for HP, Mana, Move, because that is the order they normally appear in.
  • What colours should they be? If the client doesn't really know that HP should be green, and mana blue, how do we tell it?
  • Once we get past a few bars of numbers that go up and down, what else what we might want to see? Eg. player name / class / race / level. Where would they go? In what order?


I can see the general point - the basic idea was simple, partly because it didn't try to automate too much. Once you start saying "I want the level in green in the bottom RH corner" you start introducing complexity.
Australia Forum Administrator #129
So far I am trying this:


"bar":
  "Mana":
    "max"=94
    "cur"=94
  "HP":
    "max"=43
    "cur"=23
  "Move":
    "max"=110
    "cur"=90
"level"=2
"xp":
  "max"=6509
  "cur"=2691


In other words, everything in the "bar" table is intended for your health bar. Inside that are subtables, one for each line (mana, HP, move). Per line, you get the current and max. So you can easily add guile, endurance etc. by simply adding more subtables.

Also we see our player is level 2.

Also we see his experience is 2691 / 6509 - this is used to draw the experience bar.

What the server actually sends is:


IAC SB 102
bar={HP={cur=23;max=43}; Mana={cur=94;max=94}; Move={cur=90;max=110};}; xp={cur=2691;max=6509}; level=2;
IAC SE

Amended on Tue 16 Feb 2010 11:35 PM by Nick Gammon
Australia Forum Administrator #130

Here is an example of the generic health-bar plugin I have been working on:

Australia Forum Administrator #131
What that illustrates is:

  • Player name (Nick)
  • Player level (14)
  • HP, Mana, Movement, Endurance (any number can be shown)
  • The box adjusts to how ever many stats are desired
  • In combat the background colour changes
  • Buffs (positive affects) shown under (if any), grouped (eg. Bless(2)) and in alphabetic order
  • Debuffs (negative affects) shown (if any)
  • Order of bars can be specified by "seq" parameter (only needed once)
  • Suggested colour of bars can be specified by "clr" parameter (only needed once)
  • Bars can be specified by cur/max or percent (eg. to handle case where you want to say enemy is at 40% but not give out actual HP)
  • Mouse-over any bar shows more detail (eg. HP 18/43 (41%))
  • Window can be dragged around
  • The server does not need to re-specify each value every time. For example, the level might only be specified when it changes, and the max_hp similarly.
    The player name probably only needs to be mentioned once, when you log in.


Server code to generate the box:


IAC SB 102

name="Nick";

bar={
HP={cur=18;max=43;seq=1};
Mana={cur=42;max=94;seq=2};
Move={cur=90;max=110;seq=3};
Endurance={pct=80,clr="chocolate",seq=4};
};

xp={cur=2691;max=6509};

level=14;
combat=false;

buffs={"bless", "bless", "armor", "swim", "fly", "stealth"}; 

debuffs={"blind", "sick", "sick", "poison", "stunned"}

IAC SE

Amended on Wed 17 Feb 2010 03:19 AM by Nick Gammon
Australia Forum Administrator #132
David Haley said:

A simple reason is precisely that you need all users of the protocol to agree on what the data mean, which is a very difficult task.


This is still only a suggested protocol, I don't have to convince anyone, although it seems Xtian is enthusiastic.

At the lower level, you can still just use the new telnet subnegotiation handling to manage anything you want, such as ZMP, ATCP or whatever.

Any MUD admin can choose to do:

  • Nothing at all regarding these suggestions
  • Use them unaltered, in which case they get some free, working plugins
  • Use another protocol such as ATCP
  • Devise their own improved protocol


Something like the plugin which generates the box above could indeed be distributed with MUSHclient. There is no harm if no-one uses it, and it is general enough that by adding a small amount of extra code to the server they can get the nice status box for very little work.
Australia Forum Administrator #133
If you want to play with this, the health bar is at:

http://github.com/nickgammon/plugins/blob/master/Health_Bar_Miniwindow_Telnet.xml

The experience bar is at:

http://github.com/nickgammon/plugins/blob/master/Experience_Bar_Telnet.xml

You need the new "gauge" module:

http://github.com/nickgammon/mushclient/blob/master/lua/gauge.lua

The "show status" code in SmaugFuss is now:


void show_status( CHAR_DATA *ch )
{
  
   if (WANT_TELNET_INFO (ch))
     {     
     CHAR_DATA * victim = NULL;
     
     if (ch->fighting)
       victim = ch->fighting->who;

     char * pPlayerName;
     pPlayerName = fixup_lua_strings (ch->name);
      
     char buf[MAX_STRING_LENGTH];
     snprintf(buf, sizeof (buf), 
               "\xFF\xFA\x66"         // IAC SB 102
               "tt=\"status\";"       // transaction type: status
               "name=%s;"
               "bar={"                // status bar info
               "HP={cur=%d;max=%d};"      // hp points
               "Mana={cur=%d;max=%d};"    // mana 
               "Move={cur=%d;max=%d};"    // movement
               "};"                   // end bar table
               "xp={cur=%d;max=%d};"  // experience
               "gold=%i;"             // gold
               "level=%d;"            // level
               "combat=%s;"           // in combat or not
               "dead=%s;"             // dead?
               "poisoned=%s;",        // poisoned?
               pPlayerName,
               ch->hit,
               ch->max_hit,
               IS_VAMPIRE( ch ) ? 0 : ch->mana,
               IS_VAMPIRE( ch ) ? 0 : ch->max_mana,
               ch->move,
               ch->max_move,
               ch->exp,
               exp_level( ch, ch->level + 1 ) - ch->exp,
               ch->gold,
               ch->level,
               (ch->fighting && ch->fighting->who) ? "true" : "false",
               TRUE_OR_FALSE (char_died( ch )),
               TRUE_OR_FALSE (IS_AFFECTED( ch, AFF_POISON ))
               );
      free (pPlayerName); 
           
      if( ch->first_affect )
       {
       AFFECT_DATA *paf;
       SKILLTYPE *sktmp;
       char * pAffectName;

       strncpy(&buf [strlen (buf)], "buffs={", sizeof (buf) - strlen (buf)); 
       for( paf = ch->first_affect; paf; paf = paf->next )
         {
         if (sktmp = get_skilltype( paf->type ))
            {
           pAffectName = fixup_lua_strings (sktmp->name);
            
            snprintf(&buf [strlen (buf)], sizeof (buf) - strlen (buf), 
                 "%s;", pAffectName);
                           
            free (pAffectName); 
           }
           
         } // end for loop
       strncpy(&buf [strlen (buf)], "};", sizeof (buf) - strlen (buf)); 
       }  // end if any affects
             
               
      // combat info
      if (victim)
        {
         const char * p = "You";
         char * pName;
         
         if (ch != victim)
           {
           if (IS_NPC( victim ) )
             p = victim->short_descr;
           else
             p = victim->name;
           }
      
         pName = fixup_lua_strings (p);
         
         snprintf(&buf [strlen (buf)], sizeof (buf) - strlen (buf), 
                 "victim={name=%s;" // name
                 "hp=%d;maxhp=%d;"      // hp points
                 "level=%d;"            // level
                 "};",
                 pName,
                 victim->hit,
                 victim->max_hit,
                 victim->level
               );
         free (pName); 
       }
                   
     // finish telnet negotiation after the combat info
     strncpy(&buf [strlen (buf)], "\xFF\xF0", sizeof (buf) - strlen (buf));  // IAC SE
               
     send_to_char( buf, ch );
   }
  
} 


That handles code for buffs (but not debuffs yet).

In read_from_buffer I added a couple of lines to suggest bar colours:


               write_to_descriptor(d, "\xFF\xFA\x66 tt=\"version\";version=1.0;\xFF\xF0", 0);
               // define bar colours
               write_to_descriptor(d, "\xFF\xFA\x66 bar={"
                                      "HP={clr=\"darkgreen\"};"     // hp
                                      "Mana={clr=\"mediumblue\"};"  // mana 
                                      "Move={clr=\"gold\"};"        // movement
                                      "};"                          // end bar
                                      "\xFF\xF0", 0);

Amended on Wed 17 Feb 2010 03:32 AM by Nick Gammon
USA #134
Nick Gammon said:
Something like the plugin which generates the box above could indeed be distributed with MUSHclient. There is no harm if no-one uses it, and it is general enough that by adding a small amount of extra code to the server they can get the nice status box for very little work.


Pardon the departure from the main subject, but I'd rather see very few plugins distributed with MUSHclient. It's excess clutter for everyone, on the off chance that someone might want to use it (and it's entirely possible that their MUD can't support it without modification). I can also honestly say that I've never used any of the standard plugins, and while I'm sure they're quite useful, I don't need them.

As a very brief aside, this is also one of the reasons I'd love to have a good online repository of plugins, which you can easily download other plugins from. Your GitHub repository is definitely a great start, but it's also a bit difficult to search and sort.
Australia Forum Administrator #135
I was planning to remove them, with one or two exceptions they are probably just confusing. The GitHub repository (and the plugins page) can be used to grab ones you are interested in.

I haven't forgotten the work you are doing on modularizing them, it is the sort of thing we want to get right before settling on a system.
USA #136
Thanks, Nick! I'm glad to hear it.

In regards to the modular plugins, I'll be releasing a few updated ones (including ATCP) very soon, and I helped a friend package LuaSocket directly into her plugin's libraries/ subfolder. Hopefully we'll have concrete examples to discuss in the near future!
USA #137
Nick, I kind of get the impression that you think I'm not enthusiastic about the possibilities here. I am. :) I am very much so. I've devoted a fair amount of thought, effort and conversation to this kind of protocol. I think that is why I am somewhat skeptical of simple solutions to what is a complex problem. For example, your "bar" idea is good, but makes it more difficult to express another kind of bar, for instance in an inventory window, which might display encumbrance.

Basically, we have in front of us an opportunity to do something very powerful. But we can't do that if our reaction is to say "well, it's just a suggestion for private use, so it's ok if it's not general". That's a fine goal, as I've said before; it's just a different goal.

I guess I'd like to know if we're trying to make something general or trying to make something for a special case, "whoever cares, whatever" group of people. I am very enthusiastic for the former -- otherwise I wouldn't have spent this much time here on the question ;) -- but the latter is less interesting to me (personally) because it doesn't really do something for me that I couldn't do before. Of course, the former problem -- making something general -- is also very difficult. You've already gotten a feel for some of the difficulties when doing even fairly simple things like health bars (like ordering, more kinds of bars, etc.).
#138
Quick thoughts:

- as the health-bar-language is bound to evolve, even after first public implementation, we might want to use a special namespace for it: 'tt.102bar' ... something like this? This way we will never clash with homebrew implementations on that channel (ie older Aardwolf plugins, maybe?). You must decide if this case is likely, as I dont know many plugins that are out there ...

- how about grouping the data by miniwindow/plugin/screen-area, that shall grab it: tt.healthbar.bar.cur, tt.char.dead, ... and so on. (or shorter: tt.hb.bar.cur).
That way the healthbar-miniwindow will grab everything unter tt.hb. and display it and, in the future, we can add other bar-types: tt.hb.gradientbar.cur, tt.hb.gradientbar.max.
My propositions are: hb. for the healthbar, char. for global character data (may be used in any miniwindow), status. for a wide status bar at the bottom.

- the order things shall be displayed should be the order in which the server sends the data. Give the admins that power. Think of a MUD that uses the bars: beer, bladderfullness, aspirinlevel in a north German dialect ... Alphabetical order is not predictable from the spec side of things.

- the colour of the bar should be its own field. Probably in html-like #aabbcc format: t.hb.bar.colour= #aabbcc

- if a client omits some fields that have been used before the last known values should be used

USA #139
Xtian said:
- as the health-bar-language is bound to evolve, even after first public implementation, we might want to use a special namespace for it: 'tt.102bar' ... something like this? This way we will never clash with homebrew implementations on that channel (ie older Aardwolf plugins, maybe?). You must decide if this case is likely, as I dont know many plugins that are out there ...

- how about grouping the data by miniwindow/plugin/screen-area, that shall grab it: tt.healthbar.bar.cur, tt.char.dead, ... and so on. (or shorter: tt.hb.bar.cur).
That way the healthbar-miniwindow will grab everything unter tt.hb. and display it and, in the future, we can add other bar-types: tt.hb.gradientbar.cur, tt.hb.gradientbar.max.
My propositions are: hb. for the healthbar, char. for global character data (may be used in any miniwindow), status. for a wide status bar at the bottom.

- the order things shall be displayed should be the order in which the server sends the data. Give the admins that power. Think of a MUD that uses the bars: beer, bladderfullness, aspirinlevel in a north German dialect ... Alphabetical order is not predictable from the spec side of things.


Personally, I'm a firm believer that the server shouldn't be sending presentation data unless the medium (i.e. this protocol) is explicitly intended for such. I very much prefer simply sending the semantic data, so the client can decide how to display it. What I mean is, bits of data should be named according to what they are, not how they should be displayed.

Nick's example code follows this quite well: the only presentational value is "seq", which is more of a hint and simply specifies the sequence of items like a linked list. I disagree that there should be a "bar" table at all - why can't experience be a "bar", for example? - but the rest is just data, pure and simple
#140
I understand. The analogy with html/css comes to mind. Then proposing a differentiation between "bar" and "gradientbar" wasn't a good idea.

On the other hand, you can interpret "bar" simply as a data field which has a current value and a max value. What the client/plugin displays is up to it: for example using a vertical or horizontal gauge or some kind of pie graph.

But then, the bar colour is an important aspect of that data because it directly relates to how the user understands it. We will probably always want to colour code health in green.
Australia Forum Administrator #141
Xtian said:

- as the health-bar-language is bound to evolve, even after first public implementation, we might want to use a special namespace for it: 'tt.102bar' ... something like this? This way we will never clash with homebrew implementations on that channel (ie older Aardwolf plugins, maybe?). You must decide if this case is likely, as I dont know many plugins that are out there ...


I don't really want to confuse data with presentation here.
I'm somewhat in agreement with David and Twisol here.

After all, your HP is a data field that will always have a specific meaning, whether it is shown in a bar, a box, in more than one place, or nowhere.


Xtian said:

- the order things shall be displayed should be the order in which the server sends the data. Give the admins that power. Think of a MUD that uses the bars: beer, bladderfullness, aspirinlevel in a north German dialect ... Alphabetical order is not predictable from the spec side of things.


Again, "the order the server sends the data" can be pretty hard to know. After all, I had in mind the data would arrive in pieces (eg. the max_hp only when it changes). In any case, I put in the "seq" field as a hint to the client the sequence to display in.

Xtian said:

- the colour of the bar should be its own field. Probably in html-like #aabbcc format: t.hb.bar.colour= #aabbcc


I already did that - the "clr" field.

Xtian said:

- if a client omits some fields that have been used before the last known values should be used


Did that too, in fact I mentioned in my post that my plugin tries to remember as much as it can so only a minimal amount needs to be sent when it changes.

In fact I am thinking that the server simply keeps the client up-to-date with stuff like the player's HP, mana, enemy etc. What the client does to display that information is largely up to it.
Australia Forum Administrator #142
David Haley said:

Nick, I kind of get the impression that you think I'm not enthusiastic about the possibilities here. I am. :) I am very much so. I've devoted a fair amount of thought, effort and conversation to this kind of protocol.


I'm enthusiastic that we are all working together towards the common goal of a better interface for MUD games. At the start of this thread I kicked the ball off (is that the expression?) with my original proposal, and in the light of various comments it has already been markedly improved.

I am a little concerned that we may end up spending forever on design, with little to show for it. For example, you asked why I didn't use ZMP - well I might ask "why doesn't anyone use it?". It is almost as if months, if not years of work went into designing it, but no-one wanted to actually implement it.

I am trying to juggle three things here, and by working with myself, at least at this early stage, I don't have to fight with myself to get design changes approved:

  • Changes to the client to support telnet negotiations (as implemented now)
  • Changes to the server to send relevant details, like HP, Mana etc.
  • A plugin that usefully displays all this.


As I work on this I find myself changing all three parts, and am in the happy position that I am able to do so. For example, in the process of trying to get a textured look I found that the Dissolve mode in WindowBlendImage was simply implemented wrongly (it changed the random choice for each of R, G and B, when it should have changed only once per pixel).

Then I find myself working on a generic gauge-drawing module.

I am hoping that the end product here will, at the very least, be enough knowledge for me to put forwards a design proposal that is not just theory, but demonstrably works in showing something like:

  • Player stats
  • Enemy stats during combat
  • Experience bar
  • Mobs in the current room
  • Stats of an entire group


I will continue to push my changes up to GitHub, so anyone who wants to can play with them on a private copy of SmaugFuss (or any server), and replicate my results, and perhaps make constructive suggestions about the look, or the design.
USA #143
Nick Gammon said:
For example, you asked why I didn't use ZMP - well I might ask "why doesn't anyone use it?".

This is a very good question. I think that it comes down to two main points:

- Elanthis couldn't push the protocol as a de facto matter because he doesn't write a popular client. MXP for instance gained some traction simply because zMUD and then MUSHclient supported it -- and I would imagine that you implemented it because Zugg was implementing it, and it made sense to do so for that reason.

- Elanthis wasn't willing to evangelize to client authors to convince them to implement it.

Now for what it's worth I don't view ZMP as inherently superior, and this Lua protocol has a very marked advantage of being able to send structured data. (ZMP is "flat" only; you have lists of parameters but you can't pass a table or list as a parameter.)

I wouldn't say that years went into ZMP itself, mainly because Elanthis didn't really push it beyond the initial concept. It came up again recently on MudBytes as a protocol, but it's hard for a bunch of server authors to work on something without client cooperation (and a lot of people don't really know how to write stuff for clients). And indeed until miniwindows, it was hard to find clients that supported subwindows in the first place.

Nick Gammon said:
I am hoping that the end product here will, at the very least, be enough knowledge for me to put forwards a design proposal that is not just theory, but demonstrably works in showing something like:

<stats, etc.>

I find that the really hard part comes when trying to generalize this. It's "relatively easy", from a protocol's standpoint, to design something for a particular MUD. The semantics are clear: you don't have to worry that everybody means something similar when they speak of a group of players, or mobs in a room, or even what a room is in the first place! But anyhow, the work here is mainly in getting the client to display this information; it's not easy to do that, especially if you don't have the power of miniwindows backing you up.

I think that it is, honestly, somewhat hopeless to try to create a definition of semantics that covers every MUD out there. You run into serious trouble even with relatively obvious differences like room-based vs. coordinate-based.

The advantage to communicating presentation is that the MUD can simply decide what an appropriate interface is. The disadvantages are pretty obvious too and we've discussed them before.

It might come down to having packages of sorts for various kinds of MUDs. Or, mixing presentation and data. You can communicate half-data/half-presentation fairly easily kind of like HTML: you send structured data, calling things tables, rows and columns in tables, etc., and tagging them with what kind of table/row/column they are. The client can then present this stuff how they feel like.
Contrast the above with some very generic "this is inventory: obj1, obj2, obj3".

I think that the bulk of the work here is in identifying interface concepts (gauges, tables, maps, etc.) and then figuring out what typical mappings are. You can perhaps give presentation hints, so that a client that implements basic GUI elements can still present stuff, while a client that wants to do something special can do whatever it feels like.

Even though it's difficult to ontologize all possible MUD data, in the end of the day there aren't that many ways to actually structure things. You have primitives, lists, maps, graphs. So a protocol that purports to address this general problem should let you structure data in this way, while preventing namespace collision. Then if people want to do things differently, that's fine.
Australia Forum Administrator #144
David Haley said:

The semantics are clear: you don't have to worry that everybody means something similar when they speak of a group of players, or mobs in a room, or even what a room is in the first place! But anyhow, the work here is mainly in getting the client to display this information; it's not easy to do that, especially if you don't have the power of miniwindows backing you up.


Well that is why I am really aiming at communicating low-level stuff, like your HP and your enemy's HP. A hint that they should be shown in green, and that HP is "more important" than mana is useful, particularly if someone is writing a server in (say) Chinese.

Having played a few MUD games in the past, supplemented with quite a bit of playing World Of Warcraft, Age of Conan, WarHammer, Runes of Magic, plus various single-player games, I think the basic concepts don't change that much. Players usually have some sort of health, which goes down in battle, some sort of energy or mana which they consume to do things, buffs and debuffs (which Final Fantasy calls "status effects"), often some sort of group, and usually a target of their attacks.

One of the things I am trying to do is allow for generality without getting bogged down by it (hard enough, as you know).

For example, in the screen shot I posted, the player name and level can be omitted if not known. The number of bars can be one or many. Status effects can be present or absent. If have put some time into "wrapping" code so it would theoretically handle dozens of buffs/debuffs.

Now that I have a basic "status box" going, I am making it more modular so that the enemy's box is just another instance of the same concept. Ditto for party members.

Once I have a more elaborate example going (backed up by example server code), then I would like to ask: "well, in what way does this *not* work for you?". And maybe in answering that question we might find improvements.

Meanwhile, users of other clients, who perhaps don't have miniwindows, can still pull out at least some of the data and display it in status bars, or the occasional "note" in the main output window.
USA #145
It's true that basic concepts don't often change. Things get hairy for more complex information, like quest status/journals, inventories, spell books, maps, and so forth. Some of these -- maps in particular -- are hairy to implement because the concept of a map actually changes depending on the MUD. MUDs that use rooms don't (usually) have positions-within-rooms, and MUDs that are purely coordinate-based won't have rooms at all. And then you've got the various SW MUDs, that mix rooms on planets and systems-with-coordinates in space.

Nick Gammon said:
Once I have a more elaborate example going (backed up by example server code), then I would like to ask: "well, in what way does this *not* work for you?". And maybe in answering that question we might find improvements.

This seems like a fine approach to me. That said, it would help to bring this to a larger audience of server developers, simply because I am not necessarily the best aware of what people are doing in LP-land, for example.
USA #146
Here is what I have done so far in an envy codebase:

www.admud.com/download/newscreen.html

www.admud.com/download for the plugins I've used but have since made a little different.

I've really enjoyed it so far and my playerbase has as well, not to mention, Nick has a few more mushclient users now. heh.

I'm very interested in the prospect of adding different ideas to this. I'm in the process of figuring out how to show groups, but that's probably something that we as a forum can come up with.

I love it though!
Australia Forum Administrator #147

Things are progressing:

This image shows:

  • Cooler-looking window with 3D-style borders
  • Nick (level 2) with a Bless buff
  • Group member Jerirath's stats also shown underneath
  • Combat target "carrion crawler" stats shown (almost dead)
  • Text window moved away a bit to make it less cluttered.
Amended on Thu 18 Feb 2010 03:11 AM by Nick Gammon
Australia Forum Administrator #148
The expanded out (by tprint) data structure to achieve that was:


"combat"=false
"group":
  1:
    "poisoned"=false
    "name"="Jerirath"
    "dead"=false
    "combat"=false
    "level"=2
    "xp":
      "max"=9200
      "cur"=6370
    "bar":
      "Mana":
        "max"=91
        "cur"=91
      "HP":
        "max"=44
        "cur"=44
      "Move":
        "max"=210
        "cur"=200
"xp":
  "max"=31050
  "cur"=10295
"tt"="status"
"buffs":
  1="bless"
  2="bless"
"name"="Nick"
"gold"=15310
"poisoned"=false
"level"=3
"dead"=false
"bar":
  "Mana":
    "max"=94
    "cur"=94
  "HP":
    "max"=59
    "cur"=34
  "Move":
    "max"=220
    "cur"=200


The raw data from the MUD was:


tt="status"; name="Nick"; bar={HP={cur=34; max=59}; Mana={cur=94; max=94}; Move={cur=200; max=220}; }; xp={cur=10295; max=31050}; group={[1]={name="Jerirath"; bar={HP={cur=44; max=44}; Mana={cur=91; max=91}; Move={cur=200; max=210}; }; xp={cur=6370; max=9200}; level=2; combat=false; dead=false; poisoned=false; }; }; gold=15310; level=3; combat=false; dead=false; poisoned=false; buffs={"bless"; "bless"; };


I haven't really tried to optimize it, for example the maxima could be omitted until such time as you level.
Amended on Thu 18 Feb 2010 03:18 AM by Nick Gammon
USA #149
I would suggest changing "bar" to "stat", but otherwise it looks like you've got a cool thing going there. Though might I suggest an extension to "tt"? Perhaps allow it to be something along the lines of "package.message", or even "package.subpackage.message" and so on, and implement your message-handling functions to mirror the setup (i.e. package = { subpackage = { message = function() end } }). That would allow you to implement something like ZMP's packages, provide a clean way to namespace things, and make it dead easy to add more messages and more handlers in a structured manner.
USA #150
Twisol said:
In regards to the modular plugins, I'll be releasing a few updated ones (including ATCP) very soon, and I helped a friend package LuaSocket directly into her plugin's libraries/ subfolder. Hopefully we'll have concrete examples to discuss in the near future!


To follow up on this, I've released four new plugins that use the new structured format, making a post on the Achaea forums detailing the release. [1] It has links to the pages where I've uploaded them.

[1] http://forums.achaea.com/index.php?showtopic=39955
Australia Forum Administrator #151
So the telnet negotiation works fine? I haven't heard much either way.
Australia Forum Administrator #152
Twisol said:

I would suggest changing "bar" to "stat", but otherwise it looks like you've got a cool thing going there.


Yes this is still a work in progress. Just when it looks almost finished I start realizing major parts need redesigning. :-)

Should have a cleaner version soon.
USA #153
Nick Gammon said:

So the telnet negotiation works fine? I haven't heard much either way.


Um, well I haven't really been able to use it with ATCP because I needed some way to reliably react to the IAC GA sequence after I've also parsed all the available ATCP data. I might've mentioned before that my current version uses a handwritten telnet state machine. It filters out ATCP-related messages and ships them to specific plugins that listened for them. When it spots the IAC GA, it sends a full list of the most recently received set of messages to all plugins that wanted that, too.

I'm sure the negotiation works fine now, it just didn't fit my needs.
Australia Forum Administrator #154
Can't you use the new, simpler, state machine built into MUSHclient? And just detect IAC GA yourself? Or will things happen in the wrong sequence?
USA #155
I've thought about that, and I believe things will happen in the wrong sequence. I have to scan the packet to find the IAC GA, but the telnet sub-negotiation callbacks occur after that (as they should). The issue is more about not being able to find an IAC GA at any other point in time. =/

EDIT: And I am jokingly incredulous at "simpler"! XD My parser is incredibly simple, and would probably even misbehave if a few odd byte sequences came its way. (It's in ATCP.plugin/libraries/parser.lua, if you're curious)
Amended on Thu 18 Feb 2010 07:36 PM by Twisol
Australia Forum Administrator #156
Twisol said:

(It's in ATCP.plugin/libraries/parser.lua, if you're curious)



ATCP.plugin/scripts/parser.lua

I'm not saying yours isn't simple, if the client didn't support telnet negotiation, which it hasn't until recently.

However now all that can go out the window and let the client parse in C++ rather than Lua script space. Then you simply have:


function OnPluginTelnetSubnegotiation (telnet_option, data)
  if telnet_option == 200 then
    -- process ATCP here
  end -- if
end -- function OnPluginTelnetSubnegotiation


Isn't that simpler than around 180 lines of code?
USA #157
I meant simpler as in less complicated. Mine is simple to the point of incredulity; yours is absolutely better. I was being tongue-in-cheek... although I'm more used to dry humor. ;)

(EDIT: Mine just uses a table of states and a single driving "parse" method to run through it. It's the first (and probably least-robust) method that came to mind, but then it was created for a single purpose)
Amended on Thu 18 Feb 2010 08:27 PM by Twisol
USA #158
You're too funny. ^_^

Quote:
Added plugin callback Handle_IAC_GA
nickgammon (author)
about a minute ago
Australia Forum Administrator #159
Oh, sorry. I see it now. :)

Anyway, added new plugin callback OnPlugin_IAC_GA. That tells you when the IAC GA (or IAC EOR) arrives. So now you can use the telnet negotiation, and just use:


function OnPlugin_IAC_GA ()
-- process recent telnet negotiations here
end -- function


... to process your recent ATCP stuff.
USA #160
Thanks, Nick. A new release of the ATCP plugin will have to wait until 4.50 is officially released, but this will definitely make it easier to implement.
Australia Forum Administrator #161
David Haley said:

Even though it's difficult to ontologize all possible MUD data, ...


I had to reach for the dictionary for that one. Thankfully it cleared it right up:


–verb (used with object),-gized, -giz·ing.
to express in ontological terms; regard from an ontological viewpoint.


No, wait ...

Further digging seems to indicate the presence of God in this argument. Does this refer to MUD admins (often known as Gods)?
USA #162
A quick Google suggested this definition:

http://en.wikipedia.org/wiki/Ontology_(information_science)
Quote:
In computer science and information science, an ontology is a formal representation of a set of concepts within a domain and the relationships between those concepts. It is used to reason about the properties of that domain, and may be used to define the domain.


I think David used it to mean similarly to "define"?
Australia Forum Administrator #163
So, not this meaning then?

Quote:

ontological argument

an a priori argument for the existence of God, asserting that as existence is a perfection, and as God is conceived of as the most perfect being, it follows that God must exist

Australia Forum Administrator #164

More progress:

This image shows:

  • The status boxes show your "position" (eg. standing, fighting, sleeping)
  • It now shows if you are following someone
  • The group leader is shown with a small "crown" to indicate they are in charge of the group
  • A "room" contents window has been added, which shows:
    • Players in the room with you, including their level
    • Mobs in the room with you, including their level
    • Objects in the room with you, coloured in various ways depending on type
  • The experience bar
  • The room name and exits box
USA #165
Ha! I wasn't aware that the term was so religiously loaded. I was using this definition, that Twisol cited: http://en.wikipedia.org/wiki/Ontology_(information_science)

This also gives a decent definition:
http://dictionary.reference.com/browse/ontology
(browse down to "Computing Dictionary")

In other words it means "simply" (for loose definitions of simplicity) to categorize the concepts, to define their spaces and to specify the interactions between the concepts.
Australia Forum Administrator #166
Right.

Back to the demo screenshot, this was really a proof of concept, that it is possible to make a MUD game more user-friendly, along the lines of what you expect these days from a MMO game.

In particular, at a glance you can see:

  • Your status (health etc.)
  • The status of party members
  • Who is grouped with you
  • Who the group leader is
  • Where you are
  • Where you can go
  • Who else is nearby, and their levels
  • What NPCs are nearby, and their levels
  • What objects are nearby (eg. loot, fountains)
  • How far until you level
  • What your general demeanor is (eg. asleep)
  • What buffs or debuffs affect you and party members, so you can do something about them
  • Who you are fighting and how the fight is going


Previously you had to type in lots of commands, or carefully monitor text as it flies by. I think it is easier to get a "situation at a glance" update, particularly if you have been distracted by chat in the meantime.
USA #167
Absolutely. It's looking really good! I think an easy way to build complex miniwindows would help immensely as well... now if only I could force myself back into the miniwidget framework. (I've been too caught up with the rest of my plugin stuff)
Australia Forum Administrator #168
FWIW this is the current protocol I am using ...

Groups of players


The current player, and his/her group, are sent in one message. This message has the "group" table, so that any number of members can easily be represented (eg. as [1], [2] etc.).

To identify the player for whom the message is being sent, the "me = true" boolean is true (ie. this is me).

If you are the group leader the "leader" boolean is true.

If you follow someone the "follow" field shows who that is.

For each person the "stats" sub-table identifies statistics likely to be represented in a "stats bar" (in this case HP, Mana, Move). Buffs and debuffs are in their own tables - basically just string values which the client can display.



  group = {
    [1] = {
      me = false,
      combat = false,
      leader = true,
      debuffs = {
        },
      name = "Jerirath",
      position = "standing",
      buffs = {
        [1] = "armor",
        [2] = "bless",
        [3] = "bless",
        },
      level = 4,
      xp = {
        max = 42550,
        cur = 3944,
        },
      stats = {
        Mana = {
          max = 91,
          cur = 91,
          },
        HP = {
          max = 73,
          cur = 73,
          },
        Move = {
          max = 230,
          cur = 230,
          },
        },
      },
    [2] = {
      me = true,
      combat = false,
      follow = "Jerirath",
      leader = false,
      debuffs = {
        },
      name = "Nick",
      position = "standing",
      buffs = {
        [1] = "bless",
        [2] = "bless",
        [3] = "armor",
        },
      level = 4,
      xp = {
        max = 42550,
        cur = 489,
        },
      stats = {
        Mana = {
          max = 94,
          cur = 94,
          },
        HP = {
          max = 72,
          cur = 67,
          },
        Move = {
          max = 230,
          cur = 230,
          },
        },
      },
    },

Australia Forum Administrator #169
Contents of room


The "inroom" table is used when the room contents change, eg. players or mobs enter or leave, or something is picked up or dropped.

Inside the "inroom" table are three subtables: objects, players, npcs.

This allows the client to group or colour each subgroup differently.

Objects have a "typ" field which is a string which identifies the object type. The client may sort by type, or perhaps colour things differently (eg. a fountain may be shown in blue).

Players have a level number, a name, and a flag indicating if they are in combat. If they are in combat, the unit name they are fighting is shown.

NPCs are similar to players, and also have an aggro flag to indicate if they attack unprovoked.


  inroom = {
    dark = false,
    blind = false,
    objects = {
      [1] = {
        typ = "fountain",
        dsc = "A large marble fountain gushes forth here.",
        },
      },
    players = {
      [1] = {
        fight = false,
        lvl = 4,
        dsc = "Jerirath",
        },
      },
    npcs = {
      [1] = {
        fight = false,
        lvl = 25,
        aggro = false,
        dsc = "A Lord of Healing stands here, overseeing your progress.\n\r",
        },
      },
    },


Example 2 (during fight):


inroom = {
    dark = false,
    blind = false,
    objects = {
      [1] = {
        typ = "corpse",
        dsc = "The corpse of the gnoll lies here.",
        },
      [2] = {
        typ = "blood",
        dsc = "A pool of spilled blood lies here.",
        },
      },
    players = {
      [1] = {
        fight = false,
        lvl = 4,
        dsc = "Jerirath",
        },
      },
    npcs = {
      [1] = {
        fight = true,
        lvl = 1,
        target = "YOU!",
        aggro = true,
        dsc = "A young gnoll is here ready to attack you.\n\r",
        },
      },
    },
Amended on Sat 20 Feb 2010 02:43 AM by Nick Gammon
Australia Forum Administrator #170
Movement


When you move a "movement" transaction is generated. This shows the name of the current room, and its vnum. It also indicates the visible exits and their vnums.


 move = {
    room = 10340,
    blind = false,
    exits = {
      north = 10325,
      },
    name = "Warriors South Cage",
    dark = false,
    },


The actual code sent is more compact than the examples indicate, but is semantically the same. For example, the movement transaction above actually looks like the following (excluding the spaces after the semicolons):


move={blind=false; dark=false; room=10340; name="Warriors South Cage"; exits={north=10325; }; }

Amended on Sat 20 Feb 2010 02:37 AM by Nick Gammon
Australia Forum Administrator #171
Unicode support


To support MUDs that may use non-Latin characters (eg. German, Greek, Chinese, Japanese, Russian - in fact most languages) the strings are encoded in UTF-8. The plugins at the MUSHclient end can display UTF-8 strings, so a minimal amount of work is needed to display in virtually any character set.

The fixup_lua_strings function used in the server can optionally convert 1-byte latin sequences to UTF-8 (as shown below). This lets players use characters in the range 0x80 to 0xFF without causing problems. It also has the side-effect of fixing up the IAC character, since the UTF-8 encoding of any character never has 0xFF in it.

The conversion to UTF-8 is optional, in case your MUD already uses UTF-8 internally, in which case you don't want to convert it twice.


// Author: Nick Gammon; 2nd February 2010
// Fixup for sending Lua-style strings to client
// Convert: \r \n double-quote IAC and ESC
// Note that Lua expects 0x00 to be sent as \0 however since this is a
// null-terminated string, we won't ever get 0x00
// Also puts quotes around result to save you the trouble of doing it.
// A NULL string returns the string "nil".

// Handles conversion to UFF-8 if UTF8_ENCODE defined
// You would normally define this unless the MUD already uses UTF-8 internally.
// http://www.codeguru.com/cpp/misc/misc/multi-lingualsupport/article.php/c10451

#define UTF8_ENCODE

char * fixup_lua_strings (const char * sce)
  {
  const char * p;
  char * dest;
  char * pd;
  int count = 3;  // allow for quotes and final 0x00 at end
  unsigned char c;
  
  if (sce == NULL)
    {
    dest = (char *) malloc (strlen ("nil") + 1);
    strcpy (dest, "nil");  
    return dest;
    }

  // first work out how much memory to allocate
  for (p = sce; *p; p++, count++)
    {
    c = (unsigned char) *p;
#ifdef UTF8_ENCODE
    if (c >= 0x80)
      count++;   // characters 0x80 to 0xFF require 110 000xx 10 xxxxxx
    else
#endif
      switch (c)
        {
        case '\r':   // carriage-return
        case '\n':   // newline
        case '"':    // double-quote
          count++;   // becomes \r \n and \"
          break;   
        
        // Note: will not apply if UTF-8 encoding present
        case '\xFF':  // IAC becomes \255
          count += 3;  
          break;
        
       } /* end of switch on character */

    }   /* end of counting */
  
  dest = (char *) malloc (count);
  pd = dest;
  *pd++ = '"';  // opening quote
  
  for (p = sce; *p; p++)
    {
    c = (unsigned char) *p;
#ifdef UTF8_ENCODE
    if (c >= 0x80)  // characters 0x80 to 0xFF require 110 000xx 10 xxxxxx
      {
      *pd++ = 0xC0 | (c >> 6);   // 110 000xx
      *pd++ = 0x80 | (c & 0x3F); // 10 xxxxxx
      }
    else
#endif
    switch (c)
      {
      case '\r':   // carriage-return
        memcpy (pd, "\\r", 2);
        pd += 2;
        break;
         
      case '\n':   // newline
        memcpy (pd, "\\n", 2);
        pd += 2;
        break;
        
      case '"':    // double-quote
        memcpy (pd, "\\\"", 2);
        pd += 2;
        break;
      
      // Note: will not apply if UTF-8 encoding present
      case '\xFF': // IAC
        memcpy (pd, "\\255", 4);
        pd += 4;
        break;
      
      default:
        *pd++ = c;
         break;  

      } /* end of switch on character */

     }   /* end of copying */
  
  *pd++ = '"';  // closing quote
  *pd = 0;      // terminating 0x00
  
  return dest;
}
Australia Forum Administrator #172
Just to elaborate a bit, I got rid of the idea of transaction types (which were possibly limiting anyway), and replaced them with high-level tables. The presence of a table indicates data relating to that. And by having multiple tables, fields like "name" are not confusing (ie. is name the name of a room, a player or a mob?).

So far we have:

  • inroom --> room contents
  • move --> room vnum, name, exits
  • group --> current player and his/her group
  • victim --> the unit being attacked by the current player
  • version --> simply the server version number
  • hint --> hints for displaying things like status bars
USA #173
Great! This is the kind of thing I was looking for when I suggested letting tt look like "package.message". At this point, I would also suggest allowing (even enforcing) transaction tables to be in package tables, to allow for a simple kind of namespacing.
Australia Forum Administrator #174
Not sure what you mean by "transaction tables to be in package tables" but I agree the tables are a form of namespace.

For example, move.name is a different variable to victim.name.
USA #175
I mean something like... well, lets assume something like ZMP's packages:

-- package table
subwindow = {}

subwindow.open = {
  name = "name",
  title = "title",
  width = 50,
  height = 50,
}


It's just a way to package related commands together so they don't clash, as long as the namespace/package name is unique.
Australia Forum Administrator #176
Isn't that exactly what I did? And if not, in what way?
USA #177
This all looks pretty nifty.

Is this an appropriate time to start hashing out small details? For example, the 'follows' thing should probably be based on a number of the group member, so that a group may contain NPCs with the same name. Or, the 'type' should be flag-based, not necessarily a simple type, as one could easily imagine things having several categories at once, e.g. "provides water" and "is movable".
USA #178
Nick Gammon said:

Isn't that exactly what I did? And if not, in what way?


*shake* Your transactions are all single, like "inroom" and "move" and "group". The example I gave, "subwindow.open", is a message (open) within a namespace (subwindow).

EDIT: This is entirely separate from the parameters themselves. You mentioned "victim.name" and "move.name", but name isn't a message. It's just a tagged piece of data that's important to a message.
Amended on Sat 20 Feb 2010 04:04 AM by Twisol
Australia Forum Administrator #179
Certainly we may as well get as many details solid as possible. :)

Can you group with NPCs? You can't in WoW. Or at least, you can't follow them, you get a message "you can't follow that unit".

As for the group number, I like the concept, but the problem is you can follow someone but not group with them (in Smaug and indeed in WoW too). Thus they wouldn't have a group number.

It would be nice if units had GUIDs but I doubt they have that, or do they?

As for item flags, I initially wanted to get away from sending "magic numbers" since that would tend to tie the resulting plugin to a particular MUD (eg. item type 15 is water). Thus I sent "water" instead. This allows for simple grouping (ie. you can put all the wet things together, and colour them blue).

I'm not sure what you would do, in a simple display (eg. room contents) that would require multiple flags (eg. is water, can be carried, and can be drunk). This sounds more like the level of detail you might want if you examine something.

Of course, this opens up a whole new can of worms - the idea you might mouse-over a unit and find out heaps of extra stuff (eg. class, race, alignment, reputation, level, email address etc.). This is hardly the sort of stuff you want to transmit to every client every moment someone enters a room.

The way WoW seems to do it, and the way I had an inventory plugin for Aardwolf working quite well a while back, was to only send a minimal amount of data, one of which was the GUID of the item (Global Unique ID). Then you might have a short inventory list that simply had a list of GUIDs.

When you mouse over an item the client sends back a message like "send me details on item 878989823423) and the server responds with a more detailed message, which the client then caches. This actually worked quite quickly and was pretty responsive - there was a slight delay the first time you looked at an item, and after that it was instant. Of course, nowadays you might use SQLite3 database to quickly store that sort of stuff.

It can all be made to work, and indeed I think is worth doing. However even without getting into that level of detail my initial tests indicate a quite responsive system. And I have to say it is F-U-N fun!

Being able to glance at the screen, and take in the basic information about who you are with, their status, if you are fighting or not, and if so, how the battle is going, gives the whole thing a sort of fun immediacy which gets you involved in it.
USA #180
I dipped my toes into WoW scripting a while back, and I agree that the system they have is definitely a great setup!

As for having multiple flags, send a table: {"water", "movable"} for example.
Australia Forum Administrator #181
Twisol said:

*shake* Your transactions are all single, like "inroom" and "move" and "group". The example I gave, "subwindow.open", is a message (open) within a namespace (subwindow).


The distinction between transactions and message eludes me a bit.

In fact, I think this is heading towards what I think is wrong with MXP, that it tries to describe in too much detail the presentation of the data (eg. allowing you to specify bold, underline and colour, inline with your data).

IMHO the important thing is to supply semantic information (eg. there is a mob here which is level 20 and it is aggressive). The client can then, if it wants to, put the mob's name in red, and ring a bell. Where that name goes, and if it appears on the screen at all, is a client issue, not something the server should concern itself with.

I agree that if we get to the stage of client-to-server messages (which I haven't yet) then the client may send something like "get meat from bag; eat meat", and preferably using GUIDs, so it would be "get item 38534 from bag 32193498; eat item 38534".

When you say "it isn't a message" I am trying to get a system where the server keeps the client up-to-date with the overall situation - like telling the client what is in the room with it. That isn't a message as such, it is information. I suppose it depends on how you define the word "message".

Australia Forum Administrator #182
Twisol said:

As for having multiple flags, send a table: {"water", "movable"} for example.


How would you sort such items? If you want water together for instance? I think you are grouping unlike things here, like "tall" and "red-head".

You conceivably could have another table of dispositions (eg. typ="water"; moveable=true) but maybe this level of detail is something you would get from a client-to-server query as I described before.
USA #183
Nick Gammon said:

Twisol said:

*shake* Your transactions are all single, like "inroom" and "move" and "group". The example I gave, "subwindow.open", is a message (open) within a namespace (subwindow).


The distinction between transactions and message eludes me a bit.

In fact, I think this is heading towards what I think is wrong with MXP, that it tries to describe in too much detail the presentation of the data (eg. allowing you to specify bold, underline and colour, inline with your data).

IMHO the important thing is to supply semantic information (eg. there is a mob here which is level 20 and it is aggressive). The client can then, if it wants to, put the mob's name in red, and ring a bell. Where that name goes, and if it appears on the screen at all, is a client issue, not something the server should concern itself with.

I agree that if we get to the stage of client-to-server messages (which I haven't yet) then the client may send something like "get meat from bag; eat meat", and preferably using GUIDs, so it would be "get item 38534 from bag 32193498; eat item 38534".

When you say "it isn't a message" I am trying to get a system where the server keeps the client up-to-date with the overall situation - like telling the client what is in the room with it. That isn't a message as such, it is information. I suppose it depends on how you define the word "message".


Eh, no. I'm using "transaction" and "message" interchangeably based on your original usage of "transaction". In technical terms, a message is uniquely defined by its name and the namespace(s) it's located under. It's precisely the same concept as C++ namespaces: it allows you to (a) group similar messages together under a namespace, and (b) use the same message name under other namespaces.
USA #184
Nick Gammon said:

Twisol said:

As for having multiple flags, send a table: {"water", "movable"} for example.


How would you sort such items? If you want water together for instance? I think you are grouping unlike things here, like "tall" and "red-head".

You conceivably could have another table of dispositions (eg. typ="water"; moveable=true) but maybe this level of detail is something you would get from a client-to-server query as I described before.

I don't know. It was a quick response to a problem that was mentioned, about supplying multiple flags. The two flags given were just the two previously mentioned.
Australia Forum Administrator #185
Twisol said:

Eh, no. I'm using "transaction" and "message" interchangeably based on your original usage of "transaction".


I appear to have misused the English language here.

I understand transaction to mean "something that has to be done" (eg. a banking transaction). (And looking at Wikipedia to refresh my memory, you might add "something that has to be done as a complete unit, or not at all).

I really meant that the messages passing from server to client are nothing more than notifications of some changed state, not something the client has to act on in any particular way (it may choose to display the information but it has no obligation to do so).

These are more like Windows messages, that is, a notification that some event has happened (eg. in the case of an operating system, that a key has been pressed). In my case I was notifying the client that something has changed at the server end (eg. someone left the room).
USA #186
*nod* Right, that is the kind of message I've had in mind as well.
#187
Fantastic how this is evolving.

I'll try to get a few details in:

- NPC groups: in our MUD we can group with NPCs, so I'd appreciate it if it were possible

- percent bars: An alternative to the stats that MUDs that hide some absolute numbers could use. Just send the percent value the player has achieved instead of the max value. (or send the percent value AND the current absolute value. albeit only for mouse-over)

- bar colour change: Make it possible for the MUD to change the bar colour. Example: When the HP falls beneath 10 points the MUD sends a colour change suggestion to "red"

- global warning: some messages from the MUD are important and should not be missed: You died, your buff collapsed, a reboot notice, ... We could implement such a message, that lets the client background/borders light up, make the taskbar blink ... something like this. See next item for a "msg" type.

- dedicated custom windows: I know many of my users like to extract and put channel messages in seperate windows from the normal textflow. We could abstract this by sending messages that are tagged to windows the plugin creates: msg { text= ...; window= "channels" }. Other possible fields are: warning= true (see above), clr (colour hint - or simply keep the ANSI colour).
This could also be used to extract the map to a different window, for those MUDs who have ASCII-art maps.

- is it possible to keep the MXP parsing active for contents of some of these? It would be great if inroom, msg (see above), exits were/stayed click-able.

I'm looking forward to implementing this.
xtian
USA #188
(EDIT: Nick, if I'm stepping on your toes or just plain wrong, please prod me. >_>)

Xtian said:
Fantastic how this is evolving.

I'll try to get a few details in:

- NPC groups: in our MUD we can group with NPCs, so I'd appreciate it if it were possible

- percent bars: An alternative to the stats that MUDs that hide some absolute numbers could use. Just send the percent value the player has achieved instead of the max value. (or send the percent value AND the current absolute value. albeit only for mouse-over)

- bar colour change: Make it possible for the MUD to change the bar colour. Example: When the HP falls beneath 10 points the MUD sends a colour change suggestion to "red"

- global warning: some messages from the MUD are important and should not be missed: You died, your buff collapsed, a reboot notice, ... We could implement such a message, that lets the client background/borders light up, make the taskbar blink ... something like this. See next item for a "msg" type.

The MUD can define whatever of its own messages it wants, this protocol simply defines how exactly to encode the messages and data. It's not really our job to place restrictions or limitations on specific messages.

Xtian said:
- dedicated custom windows: I know many of my users like to extract and put channel messages in seperate windows from the normal textflow. We could abstract this by sending messages that are tagged to windows the plugin creates: msg { text= ...; window= "channels" }. Other possible fields are: warning= true (see above), clr (colour hint - or simply keep the ANSI colour).
This could also be used to extract the map to a different window, for those MUDs who have ASCII-art maps.

This is more of a presentational effect. You could have a package that's explicitly intended to be used for custom windows, or the client itself could let a user handle messages and create windows themselves. It's not really something inherent to the protocol itself (or it shouldn't be), but it is handy however it's done.

Xtian said:
- is it possible to keep the MXP parsing active for contents of some of these? It would be great if inroom, msg (see above), exits were/stayed click-able.

The contents of this protocol are invisible, so MXP makes absolutely no sense within it. The client would display messages in a format that is sensible to it, which may or may not include links; it's up to the client.
Amended on Sat 20 Feb 2010 05:30 AM by Twisol
Australia Forum Administrator #189
Xtian said:

- percent bars: An alternative to the stats that MUDs that hide some absolute numbers could use. Just send the percent value the player has achieved instead of the max value. (or send the percent value AND the current absolute value. albeit only for mouse-over)


Certainly, this I have allowed for. You can either specify current and maximum, or a percentage. The difference is that if you hover over the bar, and the current and maximum are known, it will tell you (eg. 20 / 80 (25%) ). However if only a percentage is supplied then that is all it shows.
Australia Forum Administrator #190
Xtian said:

- global warning: some messages from the MUD are important and should not be missed: You died, your buff collapsed, a reboot notice, ... We could implement such a message, that lets the client background/borders light up, make the taskbar blink ... something like this. See next item for a "msg" type.


Again I think we need to distinguish between semantics and presentation. I am not envisaging the server specifically saying "put up 5 windows". However I agree that something like "MUD going down in 5 minutes" could conceivably be put into an "important warning" message type.

I think things like buffs disappearing are the sort of things that plugin writers could work on. For example, a "buff manager" plugin (there are similar things in WoW). I don't think the server should micro-manage what the client presents. Compare for example, the amount of information you could show on an iPhone, compared to a 21" monitor.
#191
Twisol said:

Xtian said:
- is it possible to keep the MXP parsing active for contents of some of these? It would be great if inroom, msg (see above), exits were/stayed click-able.

The contents of this protocol are invisible, so MXP makes absolutely no sense within it. The client would display messages in a format that is sensible to it, which may or may not include links; it's up to the client.


It makes sense as with the example I provided. It is rather a technical issue, along the lines of
- do we keep or strip ANSI codes?
- what encoding do we use?
- what do we do with IAC?

Remember: Mixing MXP Tags and ANSI codes proved to be a problem for example for all clients. It is a very real implementation issue and we should get it hammered down early.

As I said: It would be cute to have click-able room inventories. And since the "technology" is already there client and server-wise this seems reasonable to me.
USA #192
Xtian said:

It makes sense as with the example I provided. It is rather a technical issue, along the lines of
- do we keep or strip ANSI codes?
- what encoding do we use?
- what do we do with IAC?

Remember: Mixing MXP Tags and ANSI codes proved to be a problem for example for all clients. It is a very real implementation issue and we should get it hammered down early.

As I said: It would be cute to have click-able room inventories. And since the "technology" is already there client and server-wise this seems reasonable to me.

MXP isn't the only way to make things clickable...
Australia Forum Administrator #193
Twisol said:

MXP isn't the only way to make things clickable...


Absolutely. The inventory plugin I did for Aardwolf let you click on items in the inventory (I think Twisol did something similar) but did not use MXP.
Australia Forum Administrator #194
Xtian said:

Remember: Mixing MXP Tags and ANSI codes proved to be a problem for example for all clients. It is a very real implementation issue and we should get it hammered down early.


I think that was an example of the MXP protocol not specifying whether it was above or below the ANSI protocol.

In any case, I don't envisage any clash between these protocols. As far as I am concerned the telnet protocols are invisible to MXP, which may or may not be there alongside it.

Also, as I have been trying to say, I don't envisage you would say things like "show this in green".

You make an interesting point about showing your health in red as it gets low, but this is the sort of thing a custom plugin could do (eg. if health is less than 10% show it in red). I think trying to micromanage the presentation at the server end is a mistake.
#195
Nick Gammon said:

Again I think we need to distinguish between semantics and presentation. I am not envisaging the server specifically saying "put up 5 windows".
[...]
Compare for example, the amount of information you could show on an iPhone, compared to a 21" monitor.


Point. I was thinking more on the lines of semantically grouping messages together. Call what I defined as "window=..." a "flag".

Then, having the plugin opening a mini-window for (all) things semantically messages could be acceptable, no? Think of the WoW GUI where messages have a fixed place to appear - and as I said, many users do this in MUDs (I'm thinking of some IRE libraries), so there is a real world use-case for this.

Then, improving the plugin, the next step would be to handle different message flags visually differently, no?

Warning messages and channel messages should be a generalized enough concept.
USA #196
Are you talking about literal messages, like warnings or chats? I don't really think those would be sent over this protocol, but if they were, it would still be up to the client on how to display them. There are a few WoW mods that do in fact direct incoming channel messages elsewhere, IIRC - it's not hardwired, and it's still a choice by the client, not the protocol.
Amended on Sat 20 Feb 2010 05:57 AM by Twisol
#197
Nick Gammon said:

Twisol said:

MXP isn't the only way to make things clickable...


Absolutely. The inventory plugin I did for Aardwolf let you click on items in the inventory (I think Twisol did something similar) but did not use MXP.


I have no knowledge of another way of doing this? But I'm guessing you are referring to a MUD-specific, client-specific technique implemented for one plugin? But that is not what we where aiming for here.

MXP lets me tell the client: If you left-click you can view this. If you right-click you can choose between taking it, examining it, etc. ... in any language. We don't want to have such information in this spec, do we? So the best way would be to let the server pimp up the information it is sending with an already established protocol the same way it is handling text in the clear.

So the question is: Are we beneath or above MXP tag parsing, ANSI, etc?
Australia Forum Administrator #198
I think we are all on the same page here, which is gratifying.

If I may suggest, this might be the "year of the MUD".

From what I have read people are getting bored with WoW (and I am not sure the competitors really cut it). They have an expansion coming out "sometime" which might be much later this year.

If you can get MMO players onto your MUD, but present things reasonably helpfully (eg. status bars, and the other sort of stuff I am demonstrating) you might have a good chance to get quite a few new players.
USA #199
It's the most client-inspecific method of all: let the client decide how to do it. With MUSHclient, a plugin can get the message data and display a graphical representation of it in a miniwindow, with clickable hotspots if applicable. In clients that don't have graphical functionality like that, they might opt to echo a different representation of the data onto the output area, which might be clickable in a similar manner to what MXP results in.

I believe telnet subopt negotiation, of which this protocol is based on, would be beneath MXP and ANSI, because those formats are part of the text, and this protocol takes advantage of a feature of TELNET itself.


EDIT: Nick, count me in with one of the bored-of-WoW-ers. I found Achaea after quitting WoW a while back. ;)
Amended on Sat 20 Feb 2010 06:06 AM by Twisol
#200
Twisol said:

Are you talking about literal messages, like warnings or chats? I don't really think those would be sent over this protocol, but if they were, it would still be up to the client on how to display them. There are a few WoW mods that do in fact direct incoming channel messages elsewhere, IIRC - it's not hardwired, and it's still a choice by the client, not the protocol.


I was abstracting all text, grouping it into relevant categories. (at the moment this would mean for the server to double that output over the telnet SB).
The plugin could then choose what to do with it.

Warnings and channels were a minimum case that I proposed to handle specifically. As some MUDs also send maps and it would be nice to give them a window, I proposed giving every such "message category" its own window.
USA #201
Xtian said:

Warnings and channels were a minimum case that I proposed to handle specifically. As some MUDs also send maps and it would be nice to give them a window, I proposed giving every such "message category" its own window.


Sure, why not? My point is just that it's not something the protocol itself needs to worry about, but those who define their own messages, and the clients that receive them.
#202
Twisol said:

It's the most client-inspecific method of all: let the client decide how to do it.


I get the whole "separate presentation from the data" argument. Really.
But what we are doing here is giving every MUD out there a GUI design out-of-the-box. The way Nick is going to arrange and display his bars and mini-windows will be THE LOOK of thinks. The GUI standard, if you want.
Keep this in mind please, when you argue that we are not talking about layout and design. When the server sends data for a health-bar and it is automatically displayed in the way Nick programmed his generic plugin to do it, then the server is effectively assigning the client to present it in that way.

And that is the strength of the whole effort. (it works out-of-the-box).

Now, the question is, what use-cases are general enough and applicable enough for most MUDs out there. Those we should include in the plugin-spec.
A MUD/user will always be able to locally add to this with its own extensions/plugins, of course.

I tried to give an abstract semantic to categorised text and 2-3 use-cases I think that would be used by more than my MUD. That probably didn't come across clearly enough.

Then I said you could open a miniwindow for every text category the MUD sends. If that is too flexible in your opinion, don't throw away the whole idea. It could still be applied only to some special text categories.

Quote:

I believe telnet subopt negotiation, of which this protocol is based on, would be beneath MXP and ANSI, because those formats are part of the text, and this protocol takes advantage of a feature of TELNET itself.


Yes, as do I believe. So, back to my first question: Nick, would this work with your actual implementation? ;)
USA #203
Hmm, lots of posts since I last checked...

Quote:
Can you group with NPCs? You can't in WoW. Or at least, you can't follow them, you get a message "you can't follow that unit".

As Xtian said, grouping with NPC henchmen in games is not only possible, it actually happens. I'm not sure we should be taking WoW as the reference point for all aspects of the design. This is the danger in developing such a protocol with a relatively small audience: it's easy to omit things that other games are doing.

SMAUG doesn't really have GUIDs although it does have the pointer addresses, which would work at least as unique during the lifetime of the objects in question. But presumably, if a server wants to make use of session-unique numbers, they will be willing to cooperate enough to add these numbers.

Quote:
I'm not sure what you would do, in a simple display (eg. room contents) that would require multiple flags (eg. is water, can be carried, and can be drunk). This sounds more like the level of detail you might want if you examine something.

Well, you could color things if they're water, and sort them by whether you can pick them up or not; perhaps your presentation layer will entirely separate decorative objects from objects that can be manipulated. Etc.

Basically there's no reason for the inherent "type" to be water and for moveable to be a separate property. If 'water' is the type, what are fountains? What about type 'furniture'? Now fountains are either water or furniture?

This is part of what I was getting at with the complexity of ontologizing this stuff. I get nervous about fixing concepts too rigidly like enforcing types, and would prefer a more free-form list of properties simply because it's extraordinarily difficult for us to capture all existing uses, let alone predict future uses.

Quote:
Being able to glance at the screen, and take in the basic information about who you are with, their status, if you are fighting or not, and if so, how the battle is going, gives the whole thing a sort of fun immediacy which gets you involved in it.

Yes, I completely agree. Having information like this is very valuable and helps bridge the gap between purely textual and graphical games. This is partly why I'm eager to try to get it more right this time around than previous attempts made by other people.


Xtian said:
When the server sends data for a health-bar and it is automatically displayed in the way Nick programmed his generic plugin to do it, then the server is effectively assigning the client to present it in that way.

This is a point worth considering, although I don't see why there couldn't be several "skins" distributed out of the box to change how things are displayed. And remember that MUDs will have to speak this protocol for anything to actually happen, so they could provide their own data for the client to display.

I think it would be wonderful for MUDs to provide a stylesheet of sorts, for example. This way the MUD could choose colors, maybe even graphical elements, for some kind of coherent theme. It would also let the MUD choose if HP should be displayed as a horizontal or vertical bar, for instance, without changing the actual data being sent -- and without removing the freedom for the player to change things if they feel like it.
USA #204
David Haley said:

I think it would be wonderful for MUDs to provide a stylesheet of sorts, for example. This way the MUD could choose colors, maybe even graphical elements, for some kind of coherent theme. It would also let the MUD choose if HP should be displayed as a horizontal or vertical bar, for instance, without changing the actual data being sent -- and without removing the freedom for the player to change things if they feel like it.


A form of CSS itself would actually work rather nicely for that... I like the idea.
#205
What I like in this discussion is that we seem to have united all three perspectives to this: client, user, mudadmin.

As a MUD administrator my dilemma is this: I have a great game. I want people to see it and use it. But unfortunately, because of the nature of the thing, I don't have any access to the client program. The telnet client an my server a separate entities. I am not in control of how the user experiences my game and this greatly limits me. (in most games the way it is visually experienced plays a big part, thats why we buy big 3D graphic cards).
It also is a burden for the players, because they have to be advanced users before they can learn to adapt their client/GUI - with script and plugins, etc. If I had control over that, I would do it for them - at least for the inexperienced users - so that they had a better interface to my game.

... which would be more fun for them and give the game an overall better first impression.

Now, with this protocol, the real use for me is exactly to amend that situation.
To simply have another way to hide data in telnet subnegotiations is not new. The interesting thing is that, for the first time, I have more or less direct access to the GUI. Or at least I can have a GUI at all. And I can tell it what to do!

Still, nobody wants to give me full control over the GUI, I just get to give hints. But since my hints will be adequately displayed (i.e. health in a health bar) I am happy.

BTW: I see the mushclient<->aardwolf plugin collaboration as a first generation to this, making this generic plugin thing the second generation: mushclient<->everybody

xtian
#206
David Haley said:

I think it would be wonderful for MUDs to provide a stylesheet of sorts, for example. This way the MUD could choose colors, maybe even graphical elements, for some kind of coherent theme.


I'm a bit afraid this would over-complicate things for the moment. Mainly, it would increase Nicks and the mudadmins workload, making it less probable that the protocol would see the day of light or be widely adapted.

What I plan (see my other posts about incorporing MXP) is to send ANSI colour tags along with the protocol (*) information (server to client). That way the server decides which colour theme to use.

But the server may also make it possible for the player to change the server colour theme.

(*): ANSI colour for text elements like roomin. Of course for the graphical gauges we will have a "clr" hint in the datagram.
USA #207
Xtian said:
I'm a bit afraid this would over-complicate things for the moment. Mainly, it would increase Nicks and the mudadmins workload, making it less probable that the protocol would see the day of light or be widely adapted.

Hmm, not really, at least not on the server end. There will have to be some kind of default behavior anyhow (such as, oh, the one Nick has now...), so MUDs without stylesheets will simply get that default behavior. As for the client end, yes, it will mean more work, but no that much more so. And libraries like Twisol's miniwidget framework will (hopefully) make it that much easier to manipulate GUI elements.

Quote:
(*): ANSI colour for text elements like roomin. Of course for the graphical gauges we will have a "clr" hint in the datagram.

I'm not sure how this is meant to be easier than controlling the same using a CSS of sorts. In fact, you'll have to add color, background color, gradient color range, border color, oh and border thickness, but wait also border style, and then you've got text color on the background color, ......

I'm really quite opposed to the data encoding its own color, even if it's just a "hint". (Besides, the "hint" situation is either very limited, or will need to support "hints" for the entire presentation layer.) I'm not sure what is gained by encoding a 'clr' (why can't we use the full word?) hint in the message as opposed to a preamble that says something like: "all 'HP' elements of the 'prompt' message should be colored in 'red'".

That said I have no real opposition to ANSI colors being sent for things like room titles although it should be kept in mind that if the text is being rendered onto something other than the main window, this will also create more work because the client will have to add another place for parsing and switch text rendering colors partway through as it draws onto the miniwindow.
USA #208
By the way...

Xtian said:
If I had control over that, I would do it for them - at least for the inexperienced users - so that they had a better interface to my game.

... which would be more fun for them and give the game an overall better first impression.

Now, with this protocol, the real use for me is exactly to amend that situation.

I don't think you're really prevented from doing this already; you have full control over MUSHclient's miniwindowing system and can package an entire setup for your players to download, similar to what Aardwolf has done. The advantage of this protocol as I see it -- over and beyond a one-MUD-one-client integration -- is that many MUDs can use it and get a better experience out of the box.
Australia Forum Administrator #209
Xtian said:

Then I said you could open a miniwindow for every text category the MUD sends. If that is too flexible in your opinion, don't throw away the whole idea. It could still be applied only to some special text categories.

Yes, as do I believe. So, back to my first question: Nick, would this work with your actual implementation? ;)


Certainly I think that abstracting chat messages to another "message type" is possible and desirable. The channel name or type could be part of the message. Then the client may display all channels together, separate them, or simply discard some (eg. trade chat, or newbie chat). Of course, for efficiency you probably want to stop the server sending chat messages the client won't display.
Australia Forum Administrator #210
David Haley said:

As Xtian said, grouping with NPC henchmen in games is not only possible, it actually happens. I'm not sure we should be taking WoW as the reference point for all aspects of the design.


No, agreed. However WoW with its 10+ million users must be doing something fundamentally right with its overall user-interface.

David Haley said:

SMAUG doesn't really have GUIDs although it does have the pointer addresses, which would work at least as unique during the lifetime of the objects in question. But presumably, if a server wants to make use of session-unique numbers, they will be willing to cooperate enough to add these numbers.


Agreed again, and indeed Aardwolf did just that for objects.

David Haley said:

Well, you could color things if they're water, and sort them by whether you can pick them up or not; perhaps your presentation layer will entirely separate decorative objects from objects that can be manipulated. Etc.


My reference point here was what you currently see when you look around a room. A one-line description of things. Then I noticed that Smaug coloured some of them, so added enough extra information to colour them too.

I don't know that, for the purpose of a quick "glance around the room" you want to send down weight, cost, all possible flags, etc. when you have no plans to use that data.

What *might* be useful is a "can interact with" flag. Eg. in WoW things you can interact with make the mouse pointer turn into a gear shape as you move over it. So for example, fountains can be drunk from, objects can be picked up, food can be eaten, corpses can be looted. That could certainly be useful. This is heading towards what you do with MXP and its popup menus of interaction.

David Haley said:

This is part of what I was getting at with the complexity of ontologizing this stuff.


Ah, religion again. :P

David Haley said:

I get nervous about fixing concepts too rigidly like enforcing types, and would prefer a more free-form list of properties simply because it's extraordinarily difficult for us to capture all existing uses, let alone predict future uses.


What I was thinking of as a next iteration is to do this:

  • Allow free-form types to be sent which in themselves have no specific meaning
  • As part of the "hint" message state a relationship between types and the colours to display them (eg. "water" = "blue").


As for flags, I think a type is unique to the object (it is water or it is food, etc.) however other flags like ("can wear it", "can move it", "can sell it") are more like a set.

Again, I think this extra detail might be better implemented as part of something you do when you "look", "examine" or "consider" the object.

Actually in my next iteration I think I'll try to make things more efficient, thus solving these extra issues you raise (like supplying more detail) but sending even *less* data every time you walk into a room. The word "cache" springs to mind here.
Australia Forum Administrator #211
Xtian said:

Still, nobody wants to give me full control over the GUI, I just get to give hints. But since my hints will be adequately displayed (i.e. health in a health bar) I am happy.


(And with reference to the CSS idea) ....

There are two parts here. The *default* user interface, which you get with no effort, would have limited customization. Such as, specifying that certain types of bars are displayed in green, and certain types of objects in brown.

Once you start saying "I want to display my group in a completely different way" then this is where custom plugin-writers appear out of the woodwork (as they have done in their thousands for WoW) and take the raw data and re-present it.

And indeed some of those plugins (addons they call them) are themselves highly customizable.

I think, to get this general concept off the ground initially, I would not like to go too far down the path of style sheets etc., because that is just delaying a workable end result.

However if you can argue that any design aspect *absolutely prohibits* adding such functionality in the future then that design aspect would need modification.

I should add that one of the features of the Lua interface is that you can always add stuff, and indeed I am doing just that. For example, first I send down the mob's name. Later I add "level=5", but the original plugin still works, it just ignores the extra data.
USA #212
In regards to "caching", I'd prefer that the server just send GUIDs for the items, mobs, etc. and provide an API to query for more data against those GUIDs, just like WoW does. Then, you can choose whether or not to re-query next time, but all the MUD ever -has- to do is send GUIDs to mark that those entities are present.

EDIT: Also, just to note, I'd like to make sure that whatever comes out of this will be feasible to incorporate into other clients too eventually. The CSS thing would give any client a good standard to target, mapping the details to internal client-specific functionality. I think it would make it easier... but you're right, that's something to consider later on.
Amended on Sat 20 Feb 2010 08:17 PM by Twisol
Australia Forum Administrator #213
David Haley said:

I'm really quite opposed to the data encoding its own color, even if it's just a "hint". (Besides, the "hint" situation is either very limited, or will need to support "hints" for the entire presentation layer.) I'm not sure what is gained by encoding a 'clr' (why can't we use the full word?) hint in the message as opposed to a preamble that says something like: "all 'HP' elements of the 'prompt' message should be colored in 'red'".


Initially I was sending the "clr" field with each piece of data, then moved it into a hint. When being sent once it may as well be spelt in full. The first idea was just to save the number of bytes being sent.

I take your point about colours, but I was trying to make a system where the default plugin would at least show health and mana in different colours, even if they weren't called health and mana per se. And indeed maybe the CSS idea isn't so silly once you decide to send the colours once and not attach them to every message.

Already the plugin has some colours I picked out of the air for things like borders, shadows, background etc. It would be minimal work to let the server specify that once off, when you connect. And authors of other plugins could use them if they want, or ignore them.
Australia Forum Administrator #214
Twisol said:

In regards to "caching", I'd prefer that the server just send GUIDs for the items, mobs, etc. and provide an API to query for more data against those GUIDs, just like WoW does. Then, you can choose whether or not to re-query next time, but all the MUD ever -has- to do is send GUIDs to mark that those entities are present.


Which is *exactly* what I had in mind. ;)
Australia Forum Administrator #215
David Haley said:

SMAUG doesn't really have GUIDs although it does have the pointer addresses, which would work at least as unique during the lifetime of the objects in question.


Yes but then they get reused unfortunately. An address 0x123456 could be a naga one minute and a carrion crawler the next.
USA #216
Quote:
No, agreed. However WoW with its 10+ million users must be doing something fundamentally right with its overall user-interface.

Doing something right with its overall interface isn't the same thing though as making game design choices like not grouping with NPCs. So while emulating interface might not be a bad idea, emulating game design is another question -- and besides you will never be able to convince all MUD devs out there to only do what WoW does. ;)

Quote:
I don't know that, for the purpose of a quick "glance around the room" you want to send down weight, cost, all possible flags, etc. when you have no plans to use that data.

It's not so much to send stuff you don't plan to use, but rather to not lock yourself into not being able to send more stuff.

Quote:
Actually in my next iteration I think I'll try to make things more efficient, thus solving these extra issues you raise (like supplying more detail) but sending even *less* data every time you walk into a room. The word "cache" springs to mind here.

What about compressing all this using zlib or something like that? Stuff can be cached client-side, and also zlib compression will do a good job of removing repeated information.

Quote:
I take your point about colours, but I was trying to make a system where the default plugin would at least show health and mana in different colours, even if they weren't called health and mana per se.

I'm not sure how you can enforce this without the server attaching at least some semantic data along the lines of "this represents health" and "this represents mana", unless you decide that all of these things should be displayed in bars of different colors.

What I like about the CSS-like approach is that it enforces some level of consistency of tag names. It makes you think about messages as chunks of meaningful data, that will go through some coherent formatting process. I also don't think it would terribly hard to get at least some basic CSS-like information set up, and yes it would even save some data too. (But I don't view that as a huge concern, especially if stuff is being compressed. It's a nice side bonus.)
#217
Nick Gammon said:

Initially I was sending the "clr" field with each piece of data, then moved it into a hint. When being sent once it may as well be spelt in full. The first idea was just to save the number of bytes being sent.


Presuming every nearly every MUD has switched to MCCP compression this actually wouldn't be that big an issue. Our experience is that the compression has a _huge_ effect - obviously especially on repeating text.
Just a note.
USA #218
Quote:
Yes but then they get reused unfortunately. An address 0x123456 could be a naga one minute and a carrion crawler the next.

This is true, but, well, again if a server really wanted to use this functionality they would presumably add support for it (just like they'd have to support a lot of other stuff). So I guess that I don't see it as a big deal, really.
USA #219
Copying my edit into a new post, because I think I edited it in at a very unlucky moment:

Also, just to note, I'd like to make sure that whatever comes out of this will be feasible to incorporate into other clients too eventually. The CSS thing would give any client a good standard to target, mapping the details to internal client-specific functionality. I think it would make it easier... but you're right, that's something to consider later on.
USA #220
Twisol said:
Also, just to note, I'd like to make sure that whatever comes out of this will be feasible to incorporate into other clients too eventually. The CSS thing would give any client a good standard to target, mapping the details to internal client-specific functionality. I think it would make it easier... but you're right, that's something to consider later on.

I agree, and this is what I was driving toward with my comment:
"What I like about the CSS-like approach is that it enforces some level of consistency of tag names. It makes you think about messages as chunks of meaningful data, that will go through some coherent formatting process"
Australia Forum Administrator #221
Xtian said:

Presuming every nearly every MUD has switched to MCCP compression this actually wouldn't be that big an issue. Our experience is that the compression has a _huge_ effect - obviously especially on repeating text.
Just a note.


I totally agree, and in my tests, have MCCP active. The compressed data is around 5% of the uncompressed data (that is, it is saving 95% of it).

However the server still has to assemble the data, and then get it compressed.

It is surely going to be faster (and ultimately use less bandwidth) to send something like "room=34545" than:


room=34535;desc=[[
Chamber of Trials for Warriors
Here you will experience your first full combat against MOBILES, also known
as MOBS. Mobile is the name used for monsters and the like in the game.
All exits, except down, lead to a CAGE mob. Some of these cage mobs may be
aggressive and will attack you upon entering their room. As you kill them,
you will gain experience, as well as academy equipment and gold. Once you
have defeated a mob, type GET ALL CORPSE to loot the coins and equipment.
You may also type CONFIG +AUTOLOOT or CONFIG +AUTOGOLD. Autoloot will take
everything from the corpse when you kill it. Autogold will take only the
gold from the corpse. When you loot a corpse of equipment, the items are
transferred to your inventory. You may suffer injury during the battle, so
return here and type REST or SLEEP. This will speed your healing process.
When you are finished healing, type WAKE or STAND to continue.
]];


Plus once a room desc is cached, and you have a map up of some sort, you could mouse-over rooms shown on the map and their lengthy description could be pulled out of the cache.
Australia Forum Administrator #222
Ah, the pain, the pain.

A quick look at the low-level read for SmaugFUSS makes one thing spring to mind: "total rewrite".

In order to process *incoming* telnet negotiation the thing needs to be redone. For one thing, it uses zero-terminated strings, for another it looks for \n and \r in the buffer. None of this will work for data which might be an IAC SB sequence (which might have imbedded 0x00 bytes for one thing, unless we define them away).

Later on it checks that a line is not more than 254 bytes, and then it checks for things like IAC SB ... but only if they are in the same buffer.

I think the whole thing needs to be turned into a state machine. Oh well, there go a few hours.
#223
BTW, Nick: As soon as you've got some sort of more or less stable format I'd be interested of trying out implementing the server-side on my test machine. Should give us some more insights practicability-wise, before you freeze for the first public release. (but give me a few day for that)
Australia Forum Administrator #224
OK but it won't be stable for a while now because of the caching. :)

Do you use C++? I am switching from C to C++ because I want things like map and string classes. I note that SmaugFUSS compiles without errors under C++.
USA #225
Quote:
A quick look at the low-level read for SmaugFUSS makes one thing spring to mind: "total rewrite".

Heh... welcome to the club! :-)

Quote:
Do you use C++? I am switching from C to C++ because I want things like map and string classes. I note that SmaugFUSS compiles without errors under C++.

If you're actively rewriting chunks of the SmaugFUSS code, it might be nice to coordinate with the folks at smaugmuds.org. It would be nice for everybody if it had a good telnet handler; that's something that has been on the table for a while. A conversion to C++ with a general cleanup has also been planned; it's no accident that it compiles cleanly under g++. (Actually I did a good chunk of that work, esp. when it comes to string constness.)
Australia Forum Administrator #226
After looking at the code a bit more .... <sigh>.

To even do something simple like make the unprocessed text a string type rather than a fixed buffer (which will one day overflow), we need to "new" the DESCRIPTOR_DATA rather than malloc it. Then it has to be deleted.

Do you recall David, since you are the C++ expert and I'm not, if I "new" DESCRIPTOR_DATA does the default constructor for everything inside it get called? In particular, do ints, longs and so on get set to zero? And what about pointers? Do they become NULL automatically? I seem to recall not, but if they do it saves a few assignments. The existing code does a calloc which guarantees everything becomes zero but that will just crash string types.
USA #227
When you create any data structure with 'new', it calls all implicit member constructors (unless you have initialization lists). The thing is that for primitive data type constructors to be called, you need to call them explicitly. See for example:


$ cat test.cpp

#include <iostream>
using namespace std;

class C
{
    public:
        C() : i() { /* empty */ };
        int i; int j;
};

int main()
{
    C c;
    cout << c.i << endl;
    cout << c.j << endl;
    return 0;
}


$ g++ test.cpp
$ ./a.out     
0
32767
$ 


Note that i is initialized but j is not, because we called i's constructor explicitly.


By the way, I'm not sure that a std::string is really the most appropriate data structure for a network data buffer, but I guess that's another topic.

What exactly are you doing here? A few people (myself included) have started partial conversions to C++ where you have objects that are new'ed rather than calloc'ed. The internals really are a mess, and yes the "start from scratch" impulse gets very high the more you read it.
Australia Forum Administrator #228
David Haley said:

What exactly are you doing here?


I'm trying to make the server reliably accept incoming telnet subnegotiations. Also tidy it up so that it doesn't rely upon things like IAC and WILL happening to be in the same packet.

I believe I am getting there. The function read_from_buffer has had a total rewrite. It should now handle all telnet negotiations including IAC SB x <blah> IAC SE including imbedded IAC IAC. It will also handle that in the middle of text from the player.

To achieve handling any-length buffers (as previously discussed, we don't want to limit IAC subnegotiations to some arbitrary length), I changed from fixed-length buffers to std::string.

Unfortunately, to make the simple change of adding std::string into the DESCRIPTOR_DATA structure I had to spend about 2 hours finding all the places it was referred to. I suppose it was a bit obsessive, but I wanted to get rid of the linked list previously used and change the list of descriptors into a std::list.

Now the places that used to walk the list with a custom list walker now all look like this:


  for (std::list<DESCRIPTOR_DATA * >::iterator iter = descriptor_list.begin(); 
        iter != descriptor_list.end(); 
        iter++)
     {
      d = *iter;  
      
     // operate on d here ...

     }


I also plan on using std::map for the GUID storage, so I can quickly locate a pointer given a GUID or vice-versa.
USA #229
Nick Gammon said:
I also plan on using std::map for the GUID storage, so I can quickly locate a pointer given a GUID or vice-versa.

What about using your ID system?
Australia Forum Administrator #230
Ah yes, well I had to dig around to find that. And there is this problem:

Nick Gammon said:

... this is all automatic, provided you derive the class from the appropriate auto-map pointer class ...


So far I have avoided converting *all* of Smaug to using classes (and thus new and delete), because the pain I went through with descriptors would then apply to objects etc.

So as a short-term measure I have a simple map of GUID to pointer, where the GUID is incremented each time, so even if the pointer is reused the GUID will be different. At the plugin end it shouldn't matter too much, and in fact I am sending a string, so another MUD could use GUIDs with dots and things in them.

It still relies upon deleting the entry from the map when the pointer is freed, but I don't think that happens in too many places (well I hope not).

Australia Forum Administrator #231
Anyway, if you want a laugh, I was testing querying a GUID, and getting a response back, but the response was one character short, for reasons that weren't obvious.

Finally I tracked it down ... the packet ended with something like:


obj_info={guid="454";{dsc="A fountain stands here";}}


Looks OK, doesn't it?

It arrived at the client end like this:


obj_info={guid="454";{dsc="A fountain stands here";}


It turns out that calling "send_to_char" goes through colour conversion, and the sequence "}}" becomes "}".
#232
offtopic:

Nick Gammon said:

Do you use C++?


We are an LP-MUD (LDMUD to be exact: www.ldmud.eu), we write in LPC, which is based on C with some object oriented aspects. The server, called driver (see link), is like a java runtime engine, which handles connections and interpreting LPC code at runtime. We discern the driver from the mudlib per se - which is the custom world.
All objects, rooms, even the player object and the telnet engine are written in LPC.

Advantages:
- we can make changes at runtime
- a host of inexperienced programmers can do programming (well, when I say a host ... )
- LPC is a good language for beginners. no pointers like java ==> no way to crash the driver by accident.

Interestingly enough, I think the whole German MUD landscape is using ldmud, now.
USA #233
Quote:
no pointers like java ==> no way to crash the driver by accident.

Java has pointers, but no pointer arithmetic. You can still cause problems by trying to use a null pointer. I would be somewhat surprised if it were absolutely impossible to refer to null references in LPC as well, unless of course you don't have any references at all (which I would find somewhat surprising).
Australia Forum Administrator #234
A quick progress report on the proof-of-concept changes I am making ...

Most of today was spent getting the caching of items going, firstly by assigning GUIDs to objects and characters (I am presuming room vnums will do as a GUID in Smaug at least).

Then some major rehashing of the plugins to accept minimal data (ie. the GUID), check their cache, and then request the missing information. This is going reasonably well. I wanted to make sure a single player couldn't bog the server down with thousands of requests for GUIDs, so the cache requests are queued up and serviced at the rate of 10 at a time from the main update loop. The side-effect of this is that there is a delay generally of 0.25 seconds for the information to arrive. Of course, this is only if it isn't cached already.

By assigning GUIDs to mobs we can distinguish between mobs (individually) and players. So the problem of following a mob should go away.

My next project is to make the window clickable so if you see a list of mobs you can just RH click on one of them to attack it, and maybe click on an object on the ground to pick it up.

A few problems have arisen, for example, corpses decay (in Smaug at least) by the server rewriting their internal description. So it might be the same object (and thus the same GUID) but it changes from "the corpse of the naga lies here" to "the corpse of the naga is buzzing with flies" etc.

A similar problem arises with looking at the contents of the room. If you trigger a new download of information when the room contents changes, that doesn't take into account their *disposition* changing. For example, a mob standing there might change to "the naga is fighting you".

All these things mean the design is changing around a bit as I go, and it is probably simpler to get a working version going and then discuss the design, rather than trying to design it all in advance and then finding that it doesn't work for some fundamental reason.
#235
Nick Gammon said:

My next project is to make the window clickable so if you see a list of mobs you can just RH click on one of them to attack it, and maybe click on an object on the ground to pick it up.


This would cause problems for me: Firstly: The attack command you will send, will not be the same in every language. My German MUD might not understand it. Secondly: I have established that left-click will look at an object, right-click will open a pop-up menu (with MXP).
I asked if MXP would be compatible because of issues like that - in my opinion that should fall into the servers decision space and the generic plugins should only offer the flexibility for the server to express what it wishes to represent.
USA #236
Xtian said:

Nick Gammon said:

My next project is to make the window clickable so if you see a list of mobs you can just RH click on one of them to attack it, and maybe click on an object on the ground to pick it up.


This would cause problems for me: Firstly: The attack command you will send, will not be the same in every language. My German MUD might not understand it. Secondly: I have established that left-click will look at an object, right-click will open a pop-up menu (with MXP).
I asked if MXP would be compatible because of issues like that - in my opinion that should fall into the servers decision space and the generic plugins should only offer the flexibility for the server to express what it wishes to represent.


Clearly, the action command will be customizable, since this is, after all, a server-to-client protocol. Any plugin can get the appropriate fields from a server-sent message and modulate their behavior accordingly. Forgive me if I sound tetchy.
Amended on Tue 23 Feb 2010 08:09 PM by Twisol
Australia Forum Administrator #237
Xtian said:

This would cause problems for me: Firstly: The attack command you will send, will not be the same in every language. My German MUD might not understand it.


Well it would hardly be a very flexible system if "kill" was hard-coded into it would it?

I have already indicated I am supporting UTF-8 character encoding, so that you could use it for Chinese, Japanese, German, Greek, Russian etc. MUDs. So obviously some provision needs to be made to support sending commands in the language of the MUD.


Xtian said:

Secondly: I have established that left-click will look at an object, right-click will open a pop-up menu (with MXP).
I asked if MXP would be compatible because of issues like that - in my opinion that should fall into the servers decision space and the generic plugins should only offer the flexibility for the server to express what it wishes to represent.


I have no intention of parsing MXP per se. This is a system for exchanging data between client and server, whilst MXP is more of a mark-up language for textual output.

Having said that, certainly some sort of "show this menu if you click" protocol could be implemented. Although I am not sure that having to click, have a menu pop up, then scroll down to "attack" is useful in a combat situation.

Already, different object types need different actions, eg. water (drink), food (eat), enemy npc (kill), shopkeeper (trade), corpse (loot) etc.

One simple approach would be to let the server do all this. For example, if you LH or RH click on an item in the room list it simply sends:


lhclick <545847>
rhclick <445844432>


Then the server can say, "hey, s/he RH clicked on some food, s/he must want to eat it".

Alternatively, the server could send down a list of types of objects (eg. food, drink, light, wand) and a list of actions that apply for LH click and RH click, per object type. Then at least you could mouse-over for a hint (eg. "RH click to eat this").

I am still working on the proof-of-concept stage, and experimenting with that is raising interesting issues, like the above.
USA #238
Nick Gammon said:
Alternatively, the server could send down a list of types of objects (eg. food, drink, light, wand) and a list of actions that apply for LH click and RH click, per object type. Then at least you could mouse-over for a hint (eg. "RH click to eat this").


I like this approach in particular. If you have an API for requesting various sets of data given a GUID (like what we keep referring to WoW about), you could request the most common actions for that type of item, and use that to build a menu list.
Australia Forum Administrator #239

Here is a tantalizing glimpse of another project I am working on, in conjunction with the extra messages:

This image shows:

  • An automatically-generated map of Darkhaven
  • The filled-in square is where I am
  • The dashed lines indicate I know there is an exit, but don't know where it leads just yet (in other words, I need to explore there)
  • The stubby lines indicate there is an exit, but it doesn't lead to the square on the map near it (in other words, the map is not necessarily logical)
  • Depending on where you are the map reconfigures to show the rooms leading from you (in other words, the rooms that are drawn, and the ones that are not, depend on your location)
  • If you mouse-over a room it tells you its name
I am planning to have it so if you RH-click on a square it computes the fastest route to it, and takes you there. It will also colour squares differently to show shops, trainers etc.
Amended on Wed 24 Feb 2010 05:41 AM by Nick Gammon
USA #240
That looks really good. About the "stubby lines" and their predicament, the mapper system Vadimuses has on Achaea allows you to manually modify the length of an exit. So you could do, say, "vmap exit length 1" to extend the exit line so that there's one grid-space between the connected rooms. Maybe that's something to look into at some point, so it becomes possible to make the maps look better. Maybe.

Actually, better question. Does the map draw itself outwards based on your current location? If it did, it would be able to draw accurate room layouts closer to you, but if a room calculated later would fall on the same plot, it gets reduced to a stub. If that's how it works, great! I have no further criticism, I just hope it's something I'd be able to tweak for Achaea. ;)

EDIT: At a glance it doesn't seem to work quite the way I suggested, since you have the diagonal branch pointing NE inside the square getting cut off, when the next room would be closer to the player than the one visible. A simple enough way to do it, I think, is to keep two lists: one for the currently being-drawn rooms, and the next list for the ones connected to them that haven't been drawn yet, adding to the latter as you move through the former.

Sorry, I hope I'm not being overbearing about this or anything. I love what you have so far and I'm excited for how it turns out, but having been an ASCII-map cartographer on Achaea myself, I've dealt with my share of room layouts, trying very, very hard to never overlap rooms.
Amended on Wed 24 Feb 2010 05:48 AM by Twisol
Australia Forum Administrator #241
Again, a work in progress. The example I posted, you correctly deduced that it was doing a deep scan first rather than a shallow scan.

I plan to rework it so it draws 1-level out first, then 2-levels out, and so on. That way the suppression of overlaps (which is already there) will suppress the less interesting rooms (ie. the ones further away).

It is actually amazingly quick, considering it is all being drawn on-the-fly without any pre-computation.

A side-effect of doing a breadth-first approach is that you would automatically get the fastest route to any target room (because it would be the route you drew first).

In its current form the map suddenly redraws like crazy if you happen to move through one of the "stubs" because effectively its starting position has changed and everything redraws from there (probably in Lua hash order).

I have yet to wrestle with how to depict "up" and "down".
USA #242
Nick Gammon said:
Again, a work in progress. The example I posted, you correctly deduced that it was doing a deep scan first rather than a shallow scan.

I plan to rework it so it draws 1-level out first, then 2-levels out, and so on. That way the suppression of overlaps (which is already there) will suppress the less interesting rooms (ie. the ones further away).

Wonderful, that's exactly what I meant ^_^

Nick Gammon said:
In its current form the map suddenly redraws like crazy if you happen to move through one of the "stubs" because effectively its starting position has changed and everything redraws from there (probably in Lua hash order).

*nod* Right. Redrawing by distance from current location would probably make that whole problem go away.

Nick Gammon said:
I have yet to wrestle with how to depict "up" and "down".

Mm, there are two options I've seen, both of which you might want to look into. In the first, only your elevation is shown in isolation; the "search" stops at any up/down exits. In the second, similar to the exit-length thing from before, you can configure an exit to go in a different direction from what it really is. That is, an up exit could be configured to go west, for example. You could also mix the two approaches.

As for actually displaying them separately from other exits - and do keep in mind, some MUDs (such as Achaea) have in/out as well - given the first approach, I'd probably opt for coloured triangles in the corners of a room. As for the second approach - displaying up/down as flat exits - you could do something like this, except graphically with triangles:

    ^
[]------[]
     v


That'd be two triangles on either side of the line, with the upwards-pointing triangle on the half of the line going towards the room that's upwards, and the downwards triangle on the opposite end.
USA #243
What about room layouts where the reverse of an exit is not in the reverse direction that you took? E.g. a curved hallway could be entered in the southern direction on one end and exited in the eastern direction at the other end.

I don't think you'd need to support terribly wacky situations, but I'm curious what would happen in a case like that.

Also, there are MUDs that have no notion of rooms at all.
USA #244
David Haley said:

What about room layouts where the reverse of an exit is not in the reverse direction that you took? E.g. a curved hallway could be entered in the southern direction on one end and exited in the eastern direction at the other end.

I don't think you'd need to support terribly wacky situations, but I'm curious what would happen in a case like that.


I'd opt for something simple, like:

[] <---[]


David Haley said:
Also, there are MUDs that have no notion of rooms at all.

If you try to make everyone happy, nobody will be happy. With MUDs, as far as I know there really isn't much of a common denominator. At least besides Telnet. A decent, well-implemented map plugin would cover most room-based MUDs very well.
Australia Forum Administrator #245
David Haley said:

What about room layouts where the reverse of an exit is not in the reverse direction that you took?


I am now detecting that if A --> B, but the inverse does not apply, then it draws a small arrow.

David Haley said:

Also, there are MUDs that have no notion of rooms at all.


It won't work for them. :P
Australia Forum Administrator #246

This is the current look of the mapper (of course the colours can be adjusted):

This image shows:

  • The white square in the middle is where I am
  • Gold squares are healers
  • Dark green squares are vendors
  • The gray square near the bottom-right is a blacksmith
  • Purple lines are up-down lines. Where possible it draws them instead of ne/nw/se/sw. In the case of Darkhaven Square (near the middle) you see a purple top to the room box, which indicates you can go up, but the up line isn't drawn as there is already a ne/nw line.
  • You can RH click to speedwalk by the most direct route to any square.
  • If you LH click when in a shop it lists the goods in it
  • As before, the dotted lines are unexplored areas
  • There are some one-way exits visible, particularly in the arena under the academy. It looks like there are a lot of rooms in the "up" direction but they are in fact the same room. Oh well, that is what you get when the underlying design doesn't totally make sense.
  • Short "stubby" lines indicate an exit where you can go if you get closer, but because of room overlap it can only draw one room
  • The rooms are now traversed breadth-first (rather than depth-first) so the view is better the closer it is to you.
The time to draw that map on my PC was 0.009641 seconds, so it isn't exactly going to slow you down.
USA #247
Nick Gammon said:
This is the current look of the mapper (of course the colours can be adjusted):


That looks amazing. What if there's multiple "special" things in a room, though, like both a vendor and a healer? As a suggestion, I would like to see the color "cycle" every two seconds or so, on a fixed tick period. And perhaps hovering the mouse over a room would position an info tooltip (miniwindow, not tooltip-text) listing the "special" things there. "You are here", the white square, would count as one of these special things, so you don't hide other special things there.

Also, thinking of Achaea, each room has its own environment. A facility to set a room's border, and its inside color (under the "special" overlays), based on its environment would be nice. Green for forest, for example. Obviously the based-on-environment bit is MUD-specific, but if the map is configurable this way it would be pretty easy to fit in. (EDIT: Also perhaps a way to set the background color of the map itself?)

EDIT 2: Also, how does the map deal with visibility? That is, once the map overflows the borders, does it stop drawing? Achaea is.... huge. A fully-loaded map would probably take a lot more time than your partially-explored Darkhaven.
Amended on Thu 25 Feb 2010 12:21 AM by Twisol
Australia Forum Administrator #248
Twisol said:

That looks amazing. What if there's multiple "special" things in a room, though, like both a vendor and a healer? As a suggestion, I would like to see the color "cycle" every two seconds or so, on a fixed tick period. And perhaps hovering the mouse over a room would position an info tooltip (miniwindow, not tooltip-text) listing the "special" things there. "You are here", the white square, would count as one of these special things, so you don't hide other special things there.


Well you could do that of course. I am just trying to prove the idea.

Twisol said:

Also, thinking of Achaea, each room has its own environment. A facility to set a room's border, and its inside color (under the "special" overlays), based on its environment would be nice. Green for forest, for example. Obviously the based-on-environment bit is MUD-specific, but if the map is configurable this way it would be pretty easy to fit in.


Just about to do that.

Twisol said:

(EDIT: Also perhaps a way to set the background color of the map itself?)


You mean from the server? Not just now but that is an idea for setting the mood.

Twisol said:

EDIT 2: Also, how does the map deal with visibility? That is, once the map overflows the borders, does it stop drawing? Achaea is.... huge. A fully-loaded map would probably take a lot more time than your partially-explored Darkhaven.


Well there are a number of limiters to stop it drawing for hours:

  • A configurable "depth" which I currently have at 30, so it never draws more than 30 steps from where you are.
  • It won't draw on top of an existing room
  • It won't draw off the edge of the map area, so that naturally limits it anyway


The room size is configurable too, so it would be interesting to explore a whole lot of places and then "zoom out" and see how fast it draws hundreds of rooms.

Ideally it should look better than a rectangle with lots of boxes in it, however it is far, far better than what we currently have with MUSHclient and mapping.

BTW the whole thing is done in a miniwindow, this is not a client enhancement as such.
USA #249
Nick Gammon said:
Twisol said:
(EDIT: Also perhaps a way to set the background color of the map itself?)

You mean from the server? Not just now but that is an idea for setting the mood.

No, simply as a client-side configuration option.

Nick Gammon said:
Twisol said:

EDIT 2: Also, how does the map deal with visibility? That is, once the map overflows the borders, does it stop drawing? Achaea is.... huge. A fully-loaded map would probably take a lot more time than your partially-explored Darkhaven.


Well there are a number of limiters to stop it drawing for hours:


*A configurable "depth" which I currently have at 30, so it never draws more than 30 steps from where you are.

*It won't draw on top of an existing room

*It won't draw off the edge of the map area, so that naturally limits it anyway


The room size is configurable too, so it would be interesting to explore a whole lot of places and then "zoom out" and see how fast it draws hundreds of rooms.

Ideally it should look better than a rectangle with lots of boxes in it, however it is far, far better than what we currently have with MUSHclient and mapping.

Ah, that makes sense. Yes, a zoom feature would be cool!

Nick Gammon said:
BTW the whole thing is done in a miniwindow, this is not a client enhancement as such.

Of course, of course.
Australia Forum Administrator #250

Zoomed out:

Zoomed in:

Amended on Thu 25 Feb 2010 02:12 AM by Nick Gammon
Australia Forum Administrator #251

This one shows the terrain colouring:

Australia Forum Administrator #252
The source to the mapper is here:

http://github.com/nickgammon/plugins/blob/master/Mapper_Telnet.xml

It requires the special telnet codes to work without modification.

To make it work on an existing MUD you would need to work out a unique room ID for each room, somehow.

There is some discussion about the difficulties in doing this here:

Template:post=7306
Please see the forum thread: http://gammon.com.au/forum/?id=7306.


With the cooperation of MUD admins, with their cooperation I say (hint hint), any MUD could use the mapper and have a very cool automatic terrain mapping going for it.

Basically the MUD needs to send down in some format (one way is via telnet subnegotiations, another way is specially formatted message intended to be caught by triggers and then omitted from output) the following:

  • The current room id
  • What exits it has, and the room ids each exit leads to
  • Some sort of information about the room if possible, eg. terrain type, whether there is a shop there etc.
  • The room name


The mapper just uses a string room ID, so the room ID (guid) could be a number, a string (like: "forest.1234") or indeed anything that uniquely identifies rooms.

My code caches the room information, so once it has it once you don't need to request it again (all you need to know on a minute-by-minute basis is your current location, obviously, so it knows where to center the map).

So in essence, the transaction goes like this:

  • Server: you are in room 21023
  • Client: I don't know about that room, please tell me more (ie. give me info on room 21023)
  • Server: OK then: room 21023 has exits <list of exits> (eg. n:21024, s:21022, e:21025). It has name "Darkhaven Bakery" it is "indoors", and it has a shop in it.

USA #253
With Achaea, you can easily get the room number (id), exits, and name through ATCP. You can't get environment, although I suggested it a long while back. You can get the environment with the SURVEY command, though that requires gagging and a list of possible "fail" messages (in case you're underground or otherwise unable to use it). Shouldn't be too hard to fit the rest in though.
Australia Forum Administrator #254
So how many hours until the Achaea version is ready Twisol?
USA #255
Ha! It'll be a bit, I haven't had a chance to check out the code. I've been trying to get moving on my widget framework, for one thing, and for another we've got company coming over soon. But a decent mapper for Achaea is going to be awesome, and I can't wait to get my hands on it.
Australia Forum Administrator #256

This is where I am heading with this stuff:

This shows a group in a fight, with all 3 sets of bars being updated in real time. Also you see how the target is faring.

On the top RH corner is the room name, exits, description, and contents.

On the bottom LH corner is the current map, showing where I am.

On the right is the "chat and combat log".

Although this is still a text MUD, you are more involved because you see what is happening around you, where you are, how your group is going, and what is in the room with you.

Australia Forum Administrator #257

I have made a YouTube video showing all this stuff in action, so you can see it is not just theory:

Demonstrated in the video are:

  • Mapper with zoom-in and zoom-out added
  • Box at the top which shows the room name, exits, description, npcs, players, and objects
  • Health bar windows for three players (grouped) - the leader is shown with a small crown symbol
  • Health bar window of mob currently being attacked
  • Experience bar
  • Chat and combat log (ie. normal text window) in a smaller size on the bottom-right

What the video makes clear is that it all works quickly and smoothly. It is fun and engaging to play.

The video has closed captioning in case you have trouble hearing (or can't understand my mumbling). Also YouTube can automatically translate the closed captions to other languages.

The video is HD - you can see more clearly if you choose a higher resolution in YouTube.

Amended on Tue 26 Nov 2013 03:26 AM by Nick Gammon
United Kingdom #258
So your working this over in to C++ now Nick rather then C? Surely this would limit it's use for older Smaug muds (1.4/1.4a etc.) which still use C? For example I run a modified 1.4a base and wouldn't be able to move to SmaugFUSS due to the amount of work that's gone in to the base already. Do you have plan's to support the older bases?

Feel free to slap me about it I've completely mis-interrupted what's been said so far. It's late and I'm tired so brains not quite clicking full speed.
Australia Forum Administrator #259
I used C++ because it gives you the standard template library, which gives you lists, vectors, strings, and all sorts of nice stuff.

You could use C if you didn't mind putting a lot of work into doing lists etc. a bit differently.

However converting Smaug to C++ isn't that bad. I think I did it in an afternoon a while back. I think you need to change "class" to "Class" (because "class" is a C++ keyword), and find a few other problems.

There is nothing stopping someone using another language (eg. Lua, Python, C) for the server end, but it just means you have to rewrite the changes I made to suit those languages. Then the plugins will still work.
USA #260
For what it's worth, I'd like to throw my vote in with the JSON crowd, if it's not too late to change. I was looking at the 'group' example on page 12, and I realized that the format is really just a reinvented JSON. There are already a good many JSON libraries out there, for many languages. I'd also bet it's less overhead than maintaining a Lua environment simply for the purposes of parsing, and it doesn't bring with it the concerns about sending or calling functions from the data. It was mentioned that most formats have drawbacks, but since this one is semantically identical to JSON, there's no harm and a whole lot of good switching can do.

I took the time to convert the 'groups' example (page 12) to JSON. It's syntactically correct JSON (as per [1]), and you can see the clear similarities to the Lua-based format. The only semantic change I made is the ["group", {<parameters>}] root object, because it made more "abstract" sense to me than {"group": {<parameters>}}. It's a stylistic choice I suppose, but both could be effectively equal to Lua's group = {<parameters>}.

As a slightly humorous point also, you could maintain your Lua environment and include a Lua-based JSON library.

[
  "group",
  {
    "me": false,
    "combat": false,
    "leader": true,
    "debuffs": [],
    "name": "Jerirath",
    "position": "standing",
    "buffs": ["armor", "bless", "bless"],
    "level": 4,
    "xp": {
      "max": 42550,
      "cur": 3944
    },
    "stats": {
      "Mana": {
        "max": 91,
        "cur": 91
      },
      "HP": {
        "max": 73,
        "cur": 73
      },
      "Move": {
        "max": 230,
        "cur": 230
      }
    }
  },
  {
    "me": true,
    "combat": false,
    "follow": "Jerirath",
    "leader": false,
    "debuffs": [],
    "name": "Nick",
    "position": "standing",
    "buffs": ["bless", "bless", "armor"],
    "level": 4,
    "xp": {
      "max": 42550,
      "cur": 489
    },
    "stats": {
      "Mana": {
        "max": 94,
        "cur": 94
      },
      "HP": {
        "max": 72,
        "cur": 67
      },
      "Move": {
        "max": 230,
        "cur": 230
      }
    }
  }
]


See the JSON website [2] for more on JSON, as well as a comprehensive list of JSON implementations categorized by language. It's rather easier to grab one of those than somehow shoehorn Lua into, say, Visual Basic. And some languages, like Python, include JSON routines out of the box.

EDIT: fixed grammar and flow because I'm a grammar-and-flow fiend.


[1] jsonlint.com

[2] json.org
Amended on Sun 28 Feb 2010 03:55 AM by Twisol
USA #261
Just for fun (and out of boredom) I threw together a side-by-side comparison of JSON and Lua in terms of a single entry of 'groups'. Wow, haha.

       JSON                  |           Lua
-----------------------------+----------------------------
[ "group",                   |  group = {
  {                          |    {
    "me": false,             |      me = false,
    "combat": false,         |      combat = false,
    "leader": true,          |      leader = true,
    "debuffs": [             |      debuffs = {
       ],                    |        },
    "name": "Jerirath",      |      name = "Jerirath"
    "position": "standing",  |      position = "standing",
    "buffs": [               |      buffs = {
      "armor",               |        "armor",
      "bless",               |        "bless",
      "bless"                |        "bless",
       ],                    |        },
    "level": 4,              |      level = 4,
    "xp": {                  |      xp = {
      "max": 42550,          |        max = 42550,
      "cur": 3944            |        cur = 3944,
      },                     |        },
    "stats": {               |      stats = {
      "Mana": {              |        Mana = {
        "max": 91,           |          max = 91,
        "cur": 91            |          cur = 91,
        },                   |          },
      "HP": {                |        HP = {
        "max": 73,           |          max = 73,
        "cur": 73            |          cur = 73,
        },                   |          },
      "Move": {              |        Move = {
        "max": 230,          |          max = 230,
        "cur": 230           |          cur = 230,
        }                    |          },
      }                      |        },
    }                        |      },
  } ]                        |    }
Amended on Sun 28 Feb 2010 04:11 AM by Twisol
USA #262
Last post, I promise. It's worth noting that after decoding that JSON in Lua, I'm fairly sure (almost positive) that you'd access it the same way as the Lua version of the example. You would literally just use json.decode() instead of loadstring()().

Also, using the [] (array) syntax in JSON, as opposed to the {} (object) syntax, it's easier to access the message field. Using the former, you'd just use decoded_data[1] to get the message identifier, whereas with {} (and also the native Lua version, I imagine, though I can't check how you did yours) you'd have to inspect the object to discern the key first.
Australia Forum Administrator #263
I admit I didn't use the Lua engine to decode client to server, so I am open to these suggestions.

Having said that, I don't want to bloat either the client or server with another large library, so I am hoping that the JSON code has no larger a footprint than Lua.
USA #264
There's a SAX-like (event-based) JSON parser/validator library I'm looking at right now, I'm trying to see if I can add it to the Lua scripting interface in MUSHclient. Ideally I'd just push a new table on for each new object/array entered, and push key/value pairs into it as needed.

Also, I can tell you without even looking that JSON is far tinier than Lua. Most of the implementations I've seen so far consist of a single pair of .c/.h files. After all, Lua is a whole environment with syntax and function calls and a call stack. JSON is just a data format.

EDIT: Good news! YAJL (the SAX-like event-driven JSON parser) is hosted under Git, so we can take advantage of git's submodule functionality to maintain an external reference to the YAJL repository without bothering with the download-yourself-and-change-this stuff. A submodule is much like an SVN external, except it doesn't travel with the head of the repository but stays where you set it (which is generally a Good Thing).
Amended on Sun 28 Feb 2010 04:40 AM by Twisol
USA #265
I'm looking into Jansson now instead, because it looks like YAJL will be too much hassle, not least because it doesn't provide for converting to JSON as well. Jansson also uses Git, as it happens.
Amended on Sun 28 Feb 2010 05:29 AM by Twisol
USA #266
Project update: Jansson turned out to be more or less for Unix only. I finally landed on json-c, which is working extremely well, and I like its API. I had to make a change to json_object.h, changing a typedef for 'boolean' to unsigned char instead of int, to fix an incompatibility with the Platform SDK. I'll be trying to write a Lua interface to it over the next couple days; preliminary experiments are promising.
USA #267
IMO, the much harder problem isn't so much figuring out what format you send the data in, but rather what the semantic rules for the data chunks are in the first place. The main constraint to be placed on the data format is that it be expandable to some reasonable extent.

One advantage I see to using JSON is that it has a better defined format that doesn't allow the (IMO) wart-like behavior of non-primitive expressions that the current Lua thing does. (Then again, that protocol isn't really formally specified anywhere, so maybe this problem can be easily defined away once the protocol gets a specification. Again, the protocol is not just the data format but also, presumably, the meaning of the chunks.)
USA #268
Count me in for JSON too. I'm not too pleased at how it requires the extra quoting noise for all key values, but I think the benefits outweigh it. Aside from not needing to parse some loosely-defined subset of lua literals, it's also easier to distinguish true arrays from key/value pairs, a distinction lua lacks (it's a mixed benefit in the Lua language, but it's a severe drawback as a wire protocol).

As for semantics, though it's nice to have as much shared semantics as possible, you're never going to get a universal interpretation, especially when different MUD codebases start using it. Most likely you'd want to stick some kind of "type" tag in each structured message so clients knew how to interpret it. MCP has a sophisticated version-aware capability negotiation system you might want to look at and adapt for this purpose.
USA #269
Sproingie said:
it's also easier to distinguish true arrays from key/value pairs, a distinction lua lacks (it's a mixed benefit in the Lua language, but it's a severe drawback as a wire protocol).


Something I'm having trouble with when it comes to encoding a Lua table in JSON. In particular, how to convert an empty table in Lua? Will it be an array or an object? There's no way to tell. I'm opting for the "be explicit" approach, disallowing empty tables in favor of something like json.newarray() or json.newobject(). Well-formed Lua tables (with all-numeric or all-string keys) will automatically be encoded, of course.

Sproingie said:

As for semantics, though it's nice to have as much shared semantics as possible, you're never going to get a universal interpretation, especially when different MUD codebases start using it. Most likely you'd want to stick some kind of "type" tag in each structured message so clients knew how to interpret it. MCP has a sophisticated version-aware capability negotiation system you might want to look at and adapt for this purpose.


It's always possible to expand the root JSON array specification. The way I suggested before was [message, {parameters}], but to be version-aware I'm sure [message, {parameters}, version] would work just as well. Or even [[version, other protocol data], message, parameters], it depends on what you want really.
Australia Forum Administrator #270
Ah yes, well back to semantics...

I think I need to tweak my design a bit.

It is becoming increasingly obvious that there are two ways you can look at an object/npc in the game:

  1. The generic object, which the area designer made
  2. The specific instance of the object, which exists in the game world space


For example:


minvoke 8905 15
Log: Admin: minvoke 8905 15
You invoke a deer (#8905 - deer - lvl 7)

minvoke 8905 16
Log: Admin: minvoke 8905 16
You invoke a deer (#8905 - deer - lvl 7)

minvoke 8905 10
Log: Admin: minvoke 8905 10
You invoke a deer (#8905 - deer - lvl 5)

oinvoke 1510
Log: Admin: oinvoke 1510
You invoke an electrum sword (#1510 - sword electrum - lvl 65 - qty 1)

oinvoke 1510 10
Log: Admin: oinvoke 1510 10
You invoke an electrum sword (#1510 - sword electrum - lvl 10 - qty 1)
The snow comes down heavily.

oinvoke 1510 20
Log: Admin: oinvoke 1510 20
You invoke an electrum sword (#1510 - sword electrum - lvl 20 - qty 1)

oinvoke 1510 30
Log: Admin: oinvoke 1510 30
You invoke an electrum sword (#1510 - sword electrum - lvl 30 - qty 1)

inv

You are carrying:
     a longsword
     (Magical) a silvery dagger
     (Magical) (Glowing) an electrum sword (4)


Somewhat amusingly, the minvoke command disregards the level I choose and substitutes one of its own choosing.

Also, the inventory list doesn't make clear that the four electrum swords are quite different levels, it looks like you have four of the same sword.

The important thing about these things is that they have a similarity: the original vnum (eg. 8905 for the deer) and a unique quality: (the GUID, eg. 2114, 2115, 2116).

The GUID is what identifies the deer if you want a specific one (for example, you might be fighting one but not the other, or one may have wandered into a different room).

Also in the case of the swords, we need the GUID to specify if we want the level 10 sword, the level 20 sword or the level 30 sword.

But for the purposes of downloading descriptions one sword, or one deer, is as good as another one.

So it seems that for efficiency purposes the client should cache at least the generic information (eg. 8905 is a deer) but still needs extra information for the GUID (eg. deer GUID 2114 is a level 7 deer, which has 50% health left).

I am guessing at this stage that rooms are unique (ie. the vnum would adequately identify them) but no doubt this isn't strictly true? For example, in an instance, or if you have rooms generated at runtime? Can anyone clear that up?
Amended on Sun 28 Feb 2010 07:11 PM by Nick Gammon
#271
Nick Gammon said:

So it seems that for efficiency purposes the client should cache at least the generic information (eg. 8905 is a deer) but still needs extra information for the GUID (eg. deer GUID 2114 is a level 7 deer, which has 50% health left).


I think it is sufficient if the client only uses the GUID for all these purposes only. My arguments:
- the server can always match a GUID to a generic object to produce its generic data
- using two IDs for every item you are caching is more complicated than one
- it uses more bandwidth
- do players really get in contact with that many equivalent objects (of the same generic type) that the overhead this adds is justifiable?
Australia Forum Administrator #272
By definition, a global *unique* identifier does not identify things of which there are more than one.

Xtian said:

do players really get in contact with that many equivalent objects (of the same generic type) that the overhead this adds is justifiable


Well, yes they do. In my example there are 4 swords in my inventory, all different levels. There were 3 deer in the room, of different levels. I might equip one sword but not the other. I might fight one deer but not the other.

If we sent down (say) the deer's vnum (and then get a single definition of what the deer looks like) and then I decide to attack it, which of the three deer am I attacking? If they all have the same number? And if one leaves the room, wounded, and I attack vnum 8905 is that the one that left the room, or the one still there?

This is the problem with Smaug where you have things like 1.sword, 2.sword and so on, to try to distinguish items. And that falls down quickly enough if you re-order your inventory (eg. by dropping something and picking it up again).
USA #273
Nick Gammon said:
By definition, a global *unique* identifier does not identify things of which there are more than one.

Is it impossible for the server to take the GUID, look it up, and work out its model/replica ID?
Australia Forum Administrator #274
Well yes it's possible, but even sending down the vnum isn't enough, as in my example that would not distinguish between different swords created with different levels (or maybe they were enchanted or something).

However I think I *do* have the solution, but I want to test it out before presenting it. Suffice to say that a fair bit of earlier design is going out the window.
Australia Forum Administrator #275
Suffice to say for now that my tentative solution is based on the way Git works. ;)
USA #276
I like it already!
USA #277
The Lua interface to JSON is coming along nicely so far, and I've pushed my latest changes to my repository. It's not quite ready for real use, mainly because there's no decoding yet and the encoding has a few limitations, but it's getting there.

On the encoding side of things, I still need to seal up the memory leaks I create with every new JSON array or object. I also need to add new methods json.to_array and json.to_object, and a constant json.null. Both should be solved by giving each json_object* a Lua full userdata with a __gc metamethod. The current json.encode() will probably return a wrapped json_object* instead of a string, and the json_object* will have :to_string() and :to_lua() methods, since they can go either way.

Well, that's the plan, anyways.
USA #278
Made a stupid slip-up and forgot to actually push it. It's there now.
Australia Forum Administrator #279
Twisol said:

I like it already!


I'll give you a hint...

Internally, Git stores all of your files in a subdirectory of the .git directory, where the file name is the hash of the contents of the file. Thus, if two files have the same name they necessarily have the same contents.

So, when you pull a Git repository to your local disk, if you already have a file of a certain name, then you therefore have the exact contents as well. In other words, you don't need to pull copies of files of the same name "just in case" the contents have changed - they won't have.
USA #280
There is already an extremely well-defined notion of "model/replica ID" -- the vnum. You can send a GUID along with the prototype identifier for that thing. Problem solved, ne?
Australia Forum Administrator #281
No, afraid not.

For one thing, as my example earlier up shows, the same vnum can be invoked with different levels, so at the very least, the level isn't defined.

And as my earlier research showed, a vnum can be modified by the server so a corpse can change from "is slightly rotting" to "is buzzing with flies", even though it is the same object.

Even if that didn't happen, say a vnum identified "a large rat" and the MUD admin changed it to "a large bat" - if you cached that vnum as "a large rat" (on a database, for instance) how would you know the server had changed the meaning of the vnum? At the very least you would have to pass the vnum and a version number.
USA #282
The json.encode function is complete; json.decode is next. I'm really happy with how this is turning out! Here's a brief explanation of what I've done.

json.encode takes Lua data and returns a "compiled" userdata-wrapped json_object. This object has :to_json() and :to_lua() methods. :to_json() will return a valid JSON string, whereas :to_lua() will return a decoded Lua representation. The compiled json objects can also be used as part of later json.encode calls.

Three helpful other items in the json table are json.array, json.object, and json.null. json.array and json.object take a table and return a compiled JSON userdata, treating the table as an array/object and ignoring any invalid keys. json.null is a pre-generated JSON userdata representing null, equivalent to json.encode(nil).

Examples:

chunk = json.encode{1, 2, nil, 4, json.null}
print(chunk:to_json())

--[[
Output:
  [1, 2, null, 4, null]
--]]

chunk = json.encode{foo=100, bar=chunk, baz=101}
print(chunk:to_json())

--[[
Output:
  { "baz": 101, "bar": [ 1, 2, null, 4, null ], "foo": 100 }
--]]

print(json.encode{}:to_json())
print(json.encode(json.array{}):to_json())
print(json.encode(json.object{}):to_json())

--[[
Output:
  ERROR: Ambiguous, could be array or object.
  [ ]
  { }
--]]



* :to_lua() isn't implemented yet, I'll be adding it along with json.decode.

EDIT: It's hard to see the difference between {} and () in the code font, so if you're confused by that, look closely.

EDIT 2: I'm moving this to a thread in the Development forum, so this one can focus on more relevant protocol details. JSON itself is obviously relevant, but less so the integration into MUSHclient.
Amended on Mon 01 Mar 2010 06:22 AM by Twisol
USA #283
Well, either way, I would rather not have this protocol make too many assumptions about "replicas" -- the more of those you make the closer you are getting to being a SMAUG-only protocol. Your "git-like" solution for example presumably creates a hash based off of a list of contents you choose, but how do you know that list is complete with some game that makes different design decisions?

I think it's quite reasonable to make sure that the protocol supports simple situations when we can.

The only real problem in what you brought up anyhow is when things change. It's not a problem if an item has extra information: you have the individual object ID, and you have the prototype ID, therefore you can pick up the prototype's information (that is easily cached) while waiting for the individual item's information to come in. In nearly all cases, this will come in as "no changes from prototype".

The corpse example isn't the prototype changing, it's the individual object. This is indeed a problem, but it's a problem no matter what scheme you decide to use: you'll always have to go fetch new information from time to time.

Some games don't even have an instance/prototype notion as strongly as SMAUG does. Will that be supported?

Anyhow I unfortunately don't have a lot of time to go into this now, but I'd just like to mention that caution is appropriate unless being SMAUG-only is ok.
USA #284
I think that the ID itself doesn't have to be particularly standardized, as long as there's a single ID field. Whatever happens to the ID on the server side shouldn't matter. So you could validly write a server with this git-like system, and use SHA1 identifiers.-
USA #285
As long as there's a clear distinction between instance and model, and this isn't tied to any particular method of computing it, it's probably ok.

I disagree that there should be only one ID field, quite simply because that forces you into a particular way of generating that ID. If you have instance ID and model ID, you're free to use that instead of going through some other complex computation to figure out some item's "unique global hash including instance and model stuff". (The very notion sounds complicated enough, and frankly kind of like overkill in many cases.)


By the way...
Quote:
I am guessing at this stage that rooms are unique (ie. the vnum would adequately identify them) but no doubt this isn't strictly true? For example, in an instance, or if you have rooms generated at runtime? Can anyone clear that up?

There is no requirement in general at all that rooms be unique. In fact, vehicle systems tend to have prototype rooms for e.g. the cockpit and then instantiate that room many times.
USA #286
David Haley said:
I disagree that there should be only one ID field, quite simply because that forces you into a particular way of generating that ID. If you have instance ID and model ID, you're free to use that instead of going through some other complex computation to figure out some item's "unique global hash including instance and model stuff". (The very notion sounds complicated enough, and frankly kind of like overkill in many cases.)


I was thinking that the ID field could also be a JSON object or array, not just a simple number or string, to accommodate most scenarios. I don't know about other interfaces, but with the JSON interface for MUSHclient I've just finished writing, you could do something like this, treating the ID as a completely opaque object:

json_data = [=[
  {
    "foo": "bar",
    "bar": "baz",
    "id":{
      "type": "npc",
      "id": "1234567890"
    }
  }
]=]

data = json.decode(json_data):to_lua()
id = data[id]
to_server = {
  action = "take",
  object = id,
}

FooBarToServer(json.encode(to_server):to_json())


The code doesn't ever have to know what exactly the ID is composed of. If it wants to display the ID, well, I don't know.
USA #287
If the id can be a composite of several fields, that's fine. (I don't really care if it's composite using JSON or something else.) Basically, I don't want it to be a single string or number, because I think that makes it much more complicated to do item caching on worlds that treat models and instances differently. If you can assume certain guarantees about models and instances, you should be able to take advantage of them even if SMAUG (or whatever) doesn't let you make those guarantees.
USA #288
Sure, that's fine. Sensible!
Australia Forum Administrator #289
Before I present my proposed system I want to at least convince myself it will work, and am inching painstakingly towards that end.

It will however have the following characteristics:

  • Information will be cached at the client end in such a way that is is language-independent (that is, you could use Lua, JSON, or Swahili for that matter).
  • It will efficiently cache things which have the same attributes (eg. if the server generates 1000 level 4 wolves, their information only needs to be stored once).
  • Caching will survive over client restarts, or server reboots, without any problems
  • The information cached can be added to (eg. adding extra fields) without any problems
  • Server writers do not need to add any extra information, such as version numbers, to their data
  • The cached information could even be shared between servers without problems (eg. production and development servers)
  • You can change your design (eg. switch from Lua to JSON) in mid-stream without affecting the way the caching works (of course, some items may become irrelevant but they won't cause problems per se).
  • It should cope with things like corpses decaying, although I have yet to put that particular part to the test yet
USA #290
Quote:
Caching will survive over client restarts, or server reboots, without any problems

Out of curiosity, why?

On many servers, when you restart the server and recreate the world, all the GUIDs will change. Therefore your caching will no longer be relevant.

Of course for some servers, the GUIDs are unique across sessions as well as intra-session, so this would work.

Surviving over client restarts seems reasonable.

Quote:
You can change your design (eg. switch from Lua to JSON) in mid-stream without affecting the way the caching works (of course, some items may become irrelevant but they won't cause problems per se).

This seems like an over-complication IMHO.

Quote:
It should cope with things like corpses decaying, although I have yet to put that particular part to the test yet

More generally, this is the problem of items deviating from their models over time. An item's description changing over time isn't too different from an item's durability changing as it gets whacked in combat. The main difference is the frequency of these changes.



Have you considered using a temporal model of some sort, where a server can provide the GUID as well as the last time at which it was updated, which would make it easy to know if you should refresh your cache? This is easier than version numbers, IMO.
Australia Forum Administrator #291
David Haley said:

Quote:
Caching will survive over client restarts, or server reboots, without any problems

Out of curiosity, why?


Well, if you have lots of room descriptions, like:


A hollow cavern deep underground
Hollow and barren of color and light, this place is overwhelming with
sounds and moisture.  The sound of water dripping and wails of pain and
death can be heard all around you.  Whatever resides in this awful place
does not welcome strangers.


... it seems wasteful to re-download them on a server reboot if you can avoid doing it.


David Haley said:

Have you considered using a temporal model of some sort, where a server can provide the GUID as well as the last time at which it was updated, which would make it easy to know if you should refresh your cache? This is easier than version numbers, IMO.


My system - which I am getting closer to having working nicely - has more than one component.

One is a GUID, which indeed is likely to be temporary. However the attributes of the thing the GUID refers to (eg. "A large fire ant wanders here, cautious of your intrusion.") are likely to be fixed. This second part - the attributes - are what are cached.
Australia Forum Administrator #292
David Haley said:

Quote:
You can change your design (eg. switch from Lua to JSON) in mid-stream without affecting the way the caching works (of course, some items may become irrelevant but they won't cause problems per se).

This seems like an over-complication IMHO.


It's a side-effect of the simplicity of the system.
USA #293
Quote:
... it seems wasteful to re-download them on a server reboot if you can avoid doing it.

Well, it does seem wasteful. But the alternative -- of persisting across sessions -- means that GUIDs now have to be properly unique across sessions, so that you don't get object #1234 that is now a sword rather than the cached helmet that you have. In other words you force the expansion of the scope "globally unique" from not just one session, but to all sessions of the MUD. Your current GUID generation approach does not enforce this, for example: its GUIDs are unique only within one session.

Quote:
My system - which I am getting closer to having working nicely - has more than one component.

One is a GUID, which indeed is likely to be temporary. However the attributes of the thing the GUID refers to (eg. "A large fire ant wanders here, cautious of your intrusion.") are likely to be fixed. This second part - the attributes - are what are cached.

What I mean is that invariably you will need to refresh these attributes from time to time, for example as corpses deteriorate, if room descriptions change with the seasons, etc.

(Speaking of which, how will you handle dynamic room descriptions?)

My point was that a temporal (not temporary) approach can help you easily determine when a cache is stale. If you get GUID 1234 last modified time 5678, and your cache is dated 5677, you know that you need to update it.
Australia Forum Administrator #294
OK, well the solution is that we don't "just" have a GUID.

In my playing around with making these plugins work, I have identified three things that characterize a "thing" on the MUD:

  1. A unique identifier (GUID) so you know one item from another one
  2. Some fixed, or rarely-changing, attributes, like description, mob type, name, class, race, faction, level etc.
  3. Some attributes that change rapidly, for example hit points during a battle, level (for a player), mana etc.


Now the GUID is absolutely required for making sure that you address an action to the correct thing. For example, if there are five mobs in the room, and you are in a group of five, your strategy may be to hit one of them with all five of you, or to maybe sheep one, stun another, sap a third, and then take on the remaining two. Even if the mobs are in every other way identical, the GUID is needed to distinguish one identical thing from another one.

The fixed attributes (like a lengthy room description) are what are candidates to be cached, since that is likely to be the major part of the attributes, therefore taking up bandwith to transmit, but change infrequently.

Finally, the volatile attributes, like current HP, or weapon wear-and-tear, are not good candidates for caching.

My basic strategy is to send from server to client those three things (GUID, attributes, volatile attributes) however since the fixed attributes change rarely we merely send a hash of the attributes. An example of this is the "where am I" message:


location={uid="r.2";hash="390422BACF";}


This tells the player s/he is in room r.2 and the room's attributes, hashed, are 390422BACF.

Now the server knows the attributes of this room actually are:


type="room";name="Limbo";desc="You float in a formless void, detached from all sensation of physical\nmatter, surrounded by swirling glowing light which fades into the\nrelative darkness around you without any trace of edge or shadow.\n";terrain="city";area="52E6691941";


And you can test that in MUSHclient by doing this:


s = [[type="room";name="Limbo";desc="You float in a formless void, detached from all sensation of physical\nmatter, surrounded by swirling glowing light which fades into the\nrelative darkness around you without any trace of edge or shadow.\n";terrain="city";area="52E6691941";]]

print (utils.tohex (utils.md5 (s)))


It prints "390422BACF6924FE26C933EF2C26AF07" - I have chosen to truncate the hash to 10 characters (40 bits).

Now what the server does is store that text string in a STL map under the key "390422BACF".

The first thing the client does upon seeing this location message is to see if it knows of "390422BACF" in its in-memory table. If not, it looks on its SQLite database for it. And as a last resort it asks the server to please send the data corresponding to "390422BACF". The server *will* know how to do that, because it stored the data in memory the moment it produced the hash in the first place.

When the response arrives at the client end, the client saves it in memory, and writes it to the database. That way it *never* needs to ask for that particular item again. Not if the server is rebooted, not if the client is restarted, and not 10 years in the future. The reason is, that since the data is stored, keyed by the hash of the data itself, it must necessarily always be the right data. (This is how Git stores your files internally BTW).

Since the data being stored is simply a string of text, it is resistant to:

  • The data format being changed (the hash would change too)
  • More fields being added (the hash would change)
  • Spelling mistakes being corrected (the hash would change)


What the client does upon seeing the hash for "390422BACF" describing this room is to get the actual data (from memory, the database or the server) and then parse that data at runtime to extract out the room name, description, etc.


The only real potential problem is that the data format may change, or be expanded, resulting in a whole lot of stuff on the database that will never be used. To combat that you simply store it with a date/time stamp, and then periodically clean up the database by deleting old records. If they are in fact redundant, well and good, you have saved some disk space. If not, they will be re-downloaded next time they are required.

David Haley said:

What I mean is that invariably you will need to refresh these attributes from time to time, for example as corpses deteriorate, if room descriptions change with the seasons, etc.

(Speaking of which, how will you handle dynamic room descriptions?)


Well you have two alternatives. One is to simply allow the new descriptions to generate a new hash. For example, on my test database I now have a number of items like this:


dsc="The corpse of the bone naga is buzzing with flies."


So in this case, the GUID stays the same (it is the same item) but the description has changed, so a new hash has been downloaded. This is probably pretty OK, it just means that instead of one entry on the database the naga might have 5 or 6 (one for each stage of decomposition).

Alternatively, you could override the description, that is, move it to the "volatile" part of the message. This probably defeats the savings of caching a bit, but could be considered as another approach.

The same would apply to dynamic room descriptions - whatever the description is, if it is the same as another room's it will generate the same hash, if not, it will get a new one.

Amended on Wed 03 Mar 2010 05:42 AM by Nick Gammon
#295
Very interesting. Instead of the server sending a GUID/equivalence-class-ID pair it sends a GUID/hash(over applicable values) pair. Great thing is that this is rather trivially implemented on the server-side.

And of course a server _could_ choose to send the equivalence-class-ID instead of a hash-over-values if it wanted to (but omitting the guaranteed consistency you have with using a hash).

Keep up the great work! Can't wait testing a first implementation when its ready.
USA #296
I'm not sure that only 40 bits is wise if the idea is that these things stick around "forevermore" and that many items are likely to have many versions as descriptions change etc. You're increasing the probability of collision significantly. It's not so much that a trillion items isn't enough, it's that the likelihood of thinking you have something cached already when it's really something else goes up a lot.

It is good though that the distinction between volatile and generally unchanging attributes is not made explicitly. It's more like you have a list of things given to you, and then a key to a list of other things, and if the server decides to change what is and isn't considered volatile, then things "just work". That is something I was worried about: different servers might have very different notions of what is and isn't volatile and therefore it would be inappropriate for the protocol to enforce decisions about those.
Australia Forum Administrator #297
I'm worried about collisions myself, and have built code into the server to detect them. When it generates a hash it compares the value already in the table to the one that generated the hash. If they are different we have a collision and the server shuts down before that duplicated hash gets "into the wild". It is a simple matter to increase the hash size, the only reason for scaling it back it to try to reduce message sizes.

The problem with my collision detection is that it only detects a collision in one server session, however that is probably adequate given that the hashes are all generated from the same data, so next time the server reboots it should generate, by and large, the same set of hashes again.

To try to get a look at the scale of the problem I checked out from the Mangos database how many of various things it stores for WoW objects, items, quests etc. ...


18375 object templates
34393 items
329259 creature loot templates
27352 creature templates
8687 quests 
-------
719973 total


Now 2^40 gives you 1099511627776 possible hashes, so assuming that your MUD is no larger than WoW, each single item has 1,527,156 slots to choose from before it collides.

I think that probably with that many possibilities *plus* the server hash-detection code, the likelihood that a duplicate hash will get loose is acceptably low. In any case, increasing the hash size in no way compromises the general design.


I'm glad you like the general design, and in fact the volatile attributes could be added to a static attribute to override it (providing the client takes the static ones first from the database, and adds in the volatile ones afterwards, overwriting where required). Maybe there is a mob that is almost always level 10, but occasionally a level 50 version is created. In this case the "level=10" could be part of the static section, but "level=50" could be in the volatile part, overwriting it.

My initial testing shows that accessing an hashed attributes from the database take an average of 0.001 seconds, and of course is much faster if it is already in the client memory. To download an unknown one is slower, but as the server services such requests in its main comms loop, which is throttled to 0.25 seconds per pass, the average time to receive an attribute from the server is just under 0.25 seconds. In practice you don't notice this, because for one thing you are often receiving attributes from the local cache, and for another this exact same delay is built into processing commands, so a pause of 0.25 seconds is what you expect *anyway* when you type "north" or something.

USA #298
Well, that's not really how hash math works, and I agree that the chances are small, but I guess I don't see the point in adding that risk when it's so easy to reduce the probability by just adding a few characters.

I agree that it would be good that attributes in the volatile section always override any present in the hashed section. This way, if a given attribute is almost always the same, you would leave it in the hashed section, but in those few occasions where it switches you override it in the volatile section.

An example:
Normal description: Fred the Fighter is standing here.
But occasionally: Fred the Fighter is sitting here.
Or: Fred the Fighter is fighting YOU!
etc.
Especially for the fighting one, it's a transient state to begin with, so it might as well be sent along. And who knows, maybe you might want to have even that description change depending on how the fight is going.



By the way, it would be fairly easy, I think, to change the networking loop to not throttle everything at 0.25 seconds. I did that a while ago on my game. There's really no need for the server to sit there idle, so as long as you have time until the next 0.25s tick, you might as well look for and process data requests. A quarter of a second could be somewhat annoying especially when so easily avoided.
Australia Forum Administrator #299
David Haley said:

Well, that's not really how hash math works, and I agree that the chances are small, but I guess I don't see the point in adding that risk when it's so easy to reduce the probability by just adding a few characters.


How many do you suggest, and on what grounds? I am aware of things like the birthday problem where it only takes about 23 people to have an even chance of a collision of birthdays out of a set of 365 days per year.

I can certainly make it 12 characters which will be 48 bits. Would that be better do you think?
Australia Forum Administrator #300
David Haley said:

By the way, it would be fairly easy, I think, to change the networking loop to not throttle everything at 0.25 seconds. I did that a while ago on my game. There's really no need for the server to sit there idle, so as long as you have time until the next 0.25s tick, you might as well look for and process data requests. A quarter of a second could be somewhat annoying especially when so easily avoided.


I've already rejigged the comms processing, because I was finding that if there was a command in the input buffer before the request for the hash, the command would be processed first, and maybe a second command, and finally it got to the hash request a couple of seconds later.

Reworking all that has removed that problem.

You are talking to someone who puts in timing messages for stuff like this, and then spends an entire morning puzzling out why a message takes 1.0 seconds to arrive when he expected it to arrive in 0.25 seconds.

As for the delay, remember this is a once-off, when you first enter an unknown room, or see an object or mob you never saw before. Unless you are charging around like a demented bull, it is probably right that you take a few seconds to stop and read stuff like that.

Another thing is you might *want* the throttling - remember it will slow down a denial-of-service attack, where someone gets his client to request tens of thousands of hashes in quick succession.
Australia Forum Administrator #301
David Haley said:

Well, that's not really how hash math works, and I agree that the chances are small, but I guess I don't see the point in adding that risk when it's so easy to reduce the probability by just adding a few characters.


According to:

http://www.answers.com/topic/birthday-problem

Answers said:

The birthday problem in this more generic sense applies to hash functions: the expected number of N-bit hashes that can be generated before getting a collision is not 2^N, but rather only 2^(N/2).


So according to that, a 40-bit hash only covers a 20-bit space (1,048,576 items) so perhaps you are right that by increasing to 48 bits we get to hash 2^24 items without too many problems (16,777,216 items).
Amended on Thu 04 Mar 2010 05:53 AM by Nick Gammon
Australia Forum Administrator #302
I appreciate all this feedback, with some help from my friends, and some experimental work, I think we will come out with a system that demonstrably works, and also has sound theory behind it.
#303
Hashes do have collisions. It's inherent to the concept. In my opinion it is not a good idea to add computational overhead to the server to try to work around this. Either we should find a sound way for the client to resolve collisions or the server should be advised to send over something that doesn't collide, but then is not a hash per se.

Sorry, I just dont like that server-side table that keeps track of all hashes. We should probably add rooms to your WoW items calculation (with the client keeping track of room name, exits, maybe mobs) and on a 1000x1000 grid which avalon.mud.de uses for outdoor areas this data quickly becomes huge.

I think we have these choices:
- ignore collisions. augmenting the number space will help and one could argue that we can more or less do this safely without a player ever encountering a collision
- keep using the hash for quick calculations but concat some additional information to make it unique or at least make a collison less probable: "room:"+hashvalue
- use something else to keep track of consistency server-side

Australia Forum Administrator #304
Xtian said:

Sorry, I just dont like that server-side table that keeps track of all hashes. We should probably add rooms to your WoW items calculation (with the client keeping track of room name, exits, maybe mobs) and on a 1000x1000 grid which avalon.mud.de uses for outdoor areas this data quickly becomes huge.


Well, WoW doesn't have rooms, it has coordinates. Let's be clear about this, hashes are for attributes, not things like unique room names. That is the job of the GUID - which I am implementing as a 64-bit field.

Your 1000x1000 grid of rooms simply becomes a unique identifier, like room.4543.3432. There is no hash and no possibility of collision there.

The hash is used for things like mob descriptions, so hopefully you aren't saying that Avalon has 1,000,000 different mobs. Even if it did, how would a player remember one from the other?

Remember, Git is used by the Linux developers to store their source. Now they are using a longer hash I admit, and we could always lengthen the hash. But when you get to a point where the likelihood of a collision is 1 in 10 billion, you can say that is good enough.
#305
Nick Gammon said:

But when you get to a point where the likelihood of a collision is 1 in 10 billion, you can say that is good enough.


Absolutely. But then we wouldn't need a server-side hash table?


Nick Gammon said:

Your 1000x1000 grid of rooms simply becomes a unique identifier, like room.4543.3432. There is no hash and no possibility of collision there.


But most of these rooms have different descriptions (in your first example you used a hash for a room description). Take a upper limit of between 10 and 100 identical permutations. That reduces the number of hashes. But if you add another attribute beside description, it goes up again. So, a million different hashes are easily generatable.

Ah, talking about scalability on the server side, we should also not forget scalability on the client side. If a user runs through 5 to 10 rooms per second, and most of these have mobs, he can quickly accumulate thousands of entries.
#306
I don't want to sound negavtive, I just want to throw in my computational concerns early. Before they arise on production systems.
USA #307
Nick Gammon said:
As for the delay, remember this is a once-off, when you first enter an unknown room, or see an object or mob you never saw before. Unless you are charging around like a demented bull, it is probably right that you take a few seconds to stop and read stuff like that.

Does the client start loading stuff as soon as it sees stuff to load, or when the player requests the information e.g. with a miniwindow mouseover?
If the information is requested as soon as possible, then I think you're right that the delay is ok.

Nick Gammon said:
Another thing is you might *want* the throttling - remember it will slow down a denial-of-service attack, where someone gets his client to request tens of thousands of hashes in quick succession.

Personally I don't like the idea of slowing everything down just to guard against a relatively unusual case. I agree that this case needs to be protected against, but you could do that by explicitly limiting the number of requests per player to, say, 20 (??) per second, rather than slowing everything down for everybody all the time.

<will post more soon, meeting time>
USA #308
It might be worth noting that WoW has throttling - X amount of requests every Y seconds - and nobody seems to have a problem with it. You just have to be conservative with your API calls when you write plugins, which is arguably a good thing, since it can promote better code design.

EDIT: I just noticed that this is exactly what you suggest in the second half of your response, heh. Oops.
Amended on Thu 04 Mar 2010 06:46 PM by Twisol
#309
In our experience 7-10 commands per seconds is a good value. After that incoming messages will be buffered and delayed.
But connections in char-mode will need a higher value if you count each incoming character as a command.
USA #310
Nick Gammon said:
So according to that, a 40-bit hash only covers a 20-bit space (1,048,576 items) so perhaps you are right that by increasing to 48 bits we get to hash 2^24 items without too many problems (16,777,216 items).

I would be tempted to use all characters from the md5 hash. "If it's good enough for git..." I was going to say that compression would probably help here, but maybe it wouldn't be too useful after all because hashes are not likely to be very repetitive. Even so, I'm much more worried about unintentional collisions than I am about sending a few more bytes.

Even with server-side detection of collided hashes, it's still only over one session. A MUD could have many sessions with reboots etc., so I'm not sure that would be enough.

That said, we cannot avoid collisions without removing the hashing entirely. Adding information like the type is dividing the space of probabilities into smaller spaces, but it's not going to fully solve the problem.

A nice feature of Nick's approach is that it is agnostic to the instance/model difference. You simply have attributes that change a lot and attributes that change less often. As soon as you start talking about instances and models, you must make assumptions about the relationship between these two. And as soon as you make these assumptions, you considerably reduce portability to other MUDs.



Here is an idea that can help sanity checking. You can include a (small) checksum of some sort in both the volatile and mostly-static sections. This checksum would need to be constructed from truly immutable attributes, because it would have to be the same for all objects that share the hashed attributes. So, you might have something like the object type and file on disk that it comes from.

Now, when you receive the hashed attributes, you simply compare the checksums. If they're equal, you can be pretty darn sure you got the right thing. If they're unequal, you know immediately that you had a hash collision.

Note the difference with simply cutting up hash spaces into sub-spaces based on type. In this case, we have a more active collision detection method. In order to have a collision, three things must happen at the same time:

1- The two attribute specification strings must hash to the same code.
2- The two things must have the same type.
3- The two things must come from the same file on disk.

Yes, we're not truly fixing the problem, but we're introducing much more active collision detection, and by verifying those two extra things (type and storage medium address) we're pinpointing to a much larger degree where these things are from.
USA #311
Doesn't Git use SHA1, not MD5?
USA #312
Oops sorry, yes. SHA1.
Australia Forum Administrator #313
David Haley said:

Personally I don't like the idea of slowing everything down just to guard against a relatively unusual case. I agree that this case needs to be protected against, but you could do that by explicitly limiting the number of requests per player to, say, 20 (??) per second, rather than slowing everything down for everybody all the time.


Well I don't call processing things after 0.25 seconds "slowing everything down". And I already process requests in batches (10 I think at present), so there is extra throttling there.
Australia Forum Administrator #314
Xtian said:

In our experience 7-10 commands per seconds is a good value. After that incoming messages will be buffered and delayed.
But connections in char-mode will need a higher value if you count each incoming character as a command.


10 commands per second? Per player? What on earth are they typing?

And I have no intention of supporting "character" mode. Certainly things may dribble in, but they only get processed when a newline arrives.
USA #315
Nick Gammon said:

Xtian said:

In our experience 7-10 commands per seconds is a good value. After that incoming messages will be buffered and delayed.
But connections in char-mode will need a higher value if you count each incoming character as a command.


10 commands per second? Per player? What on earth are they typing?


I'm thinking aliases, maybe?
Australia Forum Administrator #316
David Haley said:

I would be tempted to use all characters from the md5 hash. "If it's good enough for git..." I was going to say that compression would probably help here, but maybe it wouldn't be too useful after all because hashes are not likely to be very repetitive. Even so, I'm much more worried about unintentional collisions than I am about sending a few more bytes.

Even with server-side detection of collided hashes, it's still only over one session. A MUD could have many sessions with reboots etc., so I'm not sure that would be enough.


Well as I said, even after a reboot, virtually everything will hash the same - these aren't GUIDs after all. In other words, before the reboot you have "a large wolf is standing here, with blood dripping from its jaws", and after the reboot you will have the same thing, and thus the same hashes.

You could make the hash larger, and with MCCP in use probably the compression would absorb a lot of that extra stuff, because if the same wolf keeps walking around the hash for it will quickly be compressed.

I'm not sure that you want to jump from 10 hash characters to 40 "just in case". Why not go to 80 then? If you can demonstrate that with (say) a 14 character hash (56 bits) that the likelihood of a collision with 100,000 known MUD objects is extremely small, then should be acceptable.

I mean, the very occasional (like 1 in 10 years maybe) incorrect message to a single player is hardly the worst problem facing MUDs these days.

I'm talking about managing risk here - there is a risk when you get in your car and drive you will be killed, and you don't stay at home until the risk can be demonstrated to be zero, now do you? People smoke, drink, drive cars, fly in aircraft, all of which activities have a well-known non-zero risk factor. If the risk of dying was 50%, then certainly you might stay home. If it is 1 in 100,000 you will probably go out.

Anyway, I want to point out that the number of characters in the hash is not central to the design. Individual MUD admins can crank up the number of character to reach their comfort level, and if a 40-character hash makes you comfortable, well and good.

The hash is being sent as a string, and stored as such at both ends. The size of the string can change over time, and in any case there is nothing in the design that fails if you make the string larger.
Australia Forum Administrator #317
Twisol said:

I'm thinking aliases, maybe?


Indeed, but a lot of MUDs throttle input anyway (Achaea springs to mind here, where you can't walk very quickly, and thus that slows down your speedwalking attempts).

Also Smaug only pulls one command at a time out of the queue every 0.25 seconds anyway, and I think starts to throw in another delay after it processes a couple of commands (the WAIT_STATE macro).

And WoW of course, has the "global cooldown". All designed to stop a single player from logging on, visiting 5 continents, and completing 20 quests in 5 seconds.
USA #318
Here's a suggestion. With every item, store a counter of sorts, which starts at 0. If two items clash, and they are demonstrably not of the same internal template (assuming the server has a concept of models and instances), increase the counter of the newer object and re-hash until it's unique.
USA #319
Nick Gammon said:

Twisol said:

I'm thinking aliases, maybe?


Indeed, but a lot of MUDs throttle input anyway (Achaea springs to mind here, where you can't walk very quickly, and thus that slows down your speedwalking attempts).


From my experience, the input is still processed, not really throttled. It's just a limit on how quickly you can move. Try doing 20 'ql' commands and one 'score', and see how soon the score comes out. (But note that the first command is always processed separately from the rest, for some reason; it's well known in combat circles, and people stick a no-op command in first so they know the rest are processed atomically.
USA #320
Nick Gammon said:
Well I don't call processing things after 0.25 seconds "slowing everything down". And I already process requests in batches (10 I think at present), so there is extra throttling there.

When I unthrottled Darkstone such that input was processed as it arrived (while still making sure that gameplay was unaltered) people immediately noticed and reported that the game was far more responsive -- even before I told them that I did anything. So I think that a quarter of a second is actually pretty noticeable.
There's a difference between how fast you walk around and how fast commands (like say, tell, etc.) are processed.

Regarding the hash, I think that the checksum idea I proposed would address the risk quite considerably. Although the hash length doesn't make assumptions about the client or server, having the extra (small) checksum does force support on both ends.
#321
Nick Gammon said:

Xtian said:

In our experience 7-10 commands per seconds is a good value. After that incoming messages will be buffered and delayed.


10 commands per second? Per player? What on earth are they typing?



With numpad walking you can reach 5 commands/s easily. Add in some triggers that fire when you enter rooms and you reach 10.
Australia Forum Administrator #322
So, no global cooldown, huh?

Anyway, I have been a bit distracted from doing another demo of the hash concept, as I have been doing a mapper for Achaea, based on the one I showed in the YouTube video.

One of the things that makes this mapper work so smoothly is that they use the telnet subnegotiation "out of band" idea themselves (using their ATCP protocol).

With the recent changes to MUSHclient it is quite trivial to pick up those messages, and act on them.

And the nice thing about the telnet messages is that they work reliably, regardless of how much the player fiddles with their prompt, or setting up short or long room-name displays etc.

In short, it "just works" (TM).

Not only does it work, but it is fun to use. You can RH click on a room and speedwalk to it by the shortest route (in the case of Achaea the short delay between movement commands is allowed for by the plugin). It shows the room name, the area name, and you can search for a particular room by name, or find nearby areas.

I am negotiating with them to add extra out-of-band data like, "is there a shop here?". We have in-principle agreement to do just that, as I believe that the way that MUDs are going to survive is for MUD client developers, and MUD server developers to work cooperatively together for the good of the community.

I will release the plugin for people to play with, as soon as these improvements are ironed out (extra data from the server, that is).

USA #323
Nick Gammon said:
I am negotiating with them to add extra out-of-band data like, "is there a shop here?". We have in-principle agreement to do just that, as I believe that the way that MUDs are going to survive is for MUD client developers, and MUD server developers to work cooperatively together for the good of the community.


Ahh yes, they mentioned they had gotten in touch with you on their blog. This might be an opportune time to say I've always wanted a Room.Environment message?
Australia Forum Administrator #324

The blog? Maybe I should read it. ;)

This is the current Achaea mapper:

USA #325
Nick Gammon said:
The blog? Maybe I should read it. ;)


Link below: [1]. The specific place it was mentioned is in a comment on Screencasting: [2].

[1] http://anvil.ironrealms.com/

[2] http://anvil.ironrealms.com/?p=150
Australia Forum Administrator #326
David Haley said:

Well, that's not really how hash math works, and I agree that the chances are small, but I guess I don't see the point in adding that risk when it's so easy to reduce the probability by just adding a few characters.


I did some tests. On the test server with the standard SmaugFUSS areas I made the server generate a hash for every mob, object, area and room. This was the results:


770 mobs, 1313 objects, 23 areas, 41676 rooms.
Size of hash_info_map is 2825


It is interesting that there must have been a *lot* of duplication (auto-generated rooms maybe?) because out of 43,782 total items we only got 2,825 hashes. It shows just how much data hashing can save, we have substantially reduced the amount that has to be sent down compared to sending the details for all 43,782 items.

The next test was to reduce the hash size until I got a collision, which happened at 6 characters (ie. 24 bits).

At 7 characters of hash (28 bits) there was no collision, although obviously we are on the cusp there.

This would appear to indicate that for safety's sake (and for larger MUDs) we should at least double the number of characters of hash (from 7 to 14), and possibly a bit more as well.

So for my test server I have increased it to 20 characters (80 bits) which allows for 1,208,925,819,614,629,174,706,176 different hashes, and should be safe for a space of 1,099,511,627,776 items.

So in other words, 14 characters should be safe on a minimal SmaugFUSS, but 20 characters should cover most MUDs.

Remember, you will have trouble storing 1,099,511,627,776 different items in memory, that is like 1 terra-items. Even if you only used 1 byte to store each one, you wouldn't fit them all in current memory. Plus you would have to hire a few trillion builders to generate all the mob/room/object descriptions.
Amended on Fri 05 Mar 2010 08:27 PM by Nick Gammon
USA #327
If you want UUIDs, why not just use real actual UUIDs, for which there is a solid spec and tons of fast solid library support? Sure they're long, but some of these complicated alternatives are looking a lot worse.

That said, a hash isn't terribly bad for a cache key either if you want to verify it against its content, but they don't do much to encode identity, so if you need a different identity across restarts, you're back to UUIDs.
Australia Forum Administrator #328
Indeed. My proposed spec which started a few pages back suggested that items be identified by at *least*:

  1. A GUID - this must be unique, is not a hash, and identifies the item on the MUD; and
  2. A hash of the attributes of the item. So for example, if the server repops "a fierce wolf" every minute, each one of those thousands of wolves has the same hash (thus saving downloading the description to the client), but each one has a unique GUID.

Australia Forum Administrator #329
Just to clarify in case I wasn't clear earlier on:

The GUID is not a hash, and is not based on one. You simply increment a number (I am using a 64-bit number). There are ways of making sure you start with a different number on each reboot (for example, taking the time and multiplying by a large-enough figure). Or, the server could write to disk every minute the largest number it has used so far, and then you just allow for the maximum number it could possibly allocate in a minute.

From my figures further up the page, the server generated 43,782 items (mobs, objects, areas, rooms) - these would all have a unique GUID. But it only used 2,825 hash entries. This is because a lot of those things shared the same attributes (mobs had the same name, objects looked the same etc.).

Now as the MUD powers along, and players kill things, and loot them, the number of GUIDs allocated would keep increasing quite rapidly. But not rapidly enough to exhaust a 64-bit GUID space (18446744073709551616 items). But the number of hashes will stay more-or-less constant, as the MUD keeps replacing mobs by ones of the same name.

And my test was a worst-case scenario - it assumes that the description of every mob, and every object, and every room, needed to be hashed and sent to the players in one session. Although probably that would eventually happen - since the hashes are supposed to last for ever.
Amended on Fri 05 Mar 2010 11:43 PM by Nick Gammon
USA #330
Briefly: in WoW, the ID that each mob has persists across death. You shouldn't have to create new GUIDs, considering that most areas only have a specific amount of mobs.
#331
For persistent GUIDs you could concat the counter you are using now with the reboot time of the server-session. This is what we do. (and a separator in between)
USA #332
Nick Gammon said:
Remember, you will have trouble storing 1,099,511,627,776 different items in memory, that is like 1 terra-items. Even if you only used 1 byte to store each one, you wouldn't fit them all in current memory. Plus you would have to hire a few trillion builders to generate all the mob/room/object descriptions.

Well don't forget that a lot of these aren't necessarily people manually creating items; every corpse has several stages of decomposition, a game with dynamic descriptions could have a description for each combination of day/night/season/etc., and so forth. Obviously this won't get you to 'terra-items' but it shows that even with a nominal space of, say, 1,000 things you might actually be dealing with well over 5,000 attribute hashes.

Twisol said:
You shouldn't have to create new GUIDs, considering that most areas only have a specific amount of mobs.

Well, until you get auto-generated mobs, or mobs that are otherwise created without going through the area reset (e.g., summoning spells). I don't see much point in persisting a mob's GUID beyond its own death, considering that we don't need to worry about keeping enough GUIDs around.
Australia Forum Administrator #333
Xtian said:

For persistent GUIDs you could concat the counter you are using now with the reboot time of the server-session. This is what we do. (and a separator in between)


That's a sensible idea. Then the internal number can start at 1 and just be concatenated with a fairly small boot time number. That should allow for any contingency.
USA #334
I have just found out that when I snoop a player, all my plugins break and I can see their show_status.

Guess I'll have to figure out a 'workaround' on this.


***EDIT***
What I did was put the show_status below the snoop part and it's working fine now.
Amended on Sat 13 Mar 2010 09:18 AM by Orik
Finland #335
a quick question:
I need to access global stuff so what should I replace "setfenv" with?
Australia Forum Administrator #336
You can do the "loadstring" without preceding it with setfenv. Then whatever is in the string has access to global variables, and potentially it might change them too.
#337
I have show_status working on an SWR and its updating properly. First off, I have to say this has been a really awesome advancement and has opened the door for alot in the SWR community. Currently, we are working on some graphical output for space combat and the likes.

The biggest complaint I have gotten out of it from others is the fact that it sends a prompt everytime the out of band messages are sent. Is there any way to avoid this?
Australia Forum Administrator #338
On my version it sends a blank line. Kinda annoying but since it is just for testing I just install my "omit blank lines plugin".

There must be a good reason, you would have to trace through the code (maybe use gdb) to work out why the out-of-band stuff triggers the prompt.
Germany #339
Keirath said:
The biggest complaint I have gotten out of it from others is the fact that it sends a prompt everytime the out of band messages are sent. Is there any way to avoid this?

The approach I used in my MSDP snippet is to set a flag whenever out-of-band data is sent to the client, then the mud checks the flag before sending a prompt or blank line. Sending a new command clears the flag.