Register forum user name Search FAQ

Gammon Forum

Notice: Any messages purporting to come from this site telling you that your password has expired, or that you need to verify your details, confirm your email, resolve issues, making threats, or asking for money, are spam. We do not email users with any such messages. If you have lost your password you can obtain a new one by using the password reset link.

Due to spam on this forum, all posts now need moderator approval.

 Entire forum ➜ MUDs ➜ General ➜ Suggested protocol for server to client "out of band" messages

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

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


Pages: 1 2  3  4  5  6  7  8  9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  

Posted by Nick Gammon   Australia  (23,133 posts)  Bio   Forum Administrator
Date Tue 02 Feb 2010 08:11 PM (UTC)

Amended on Tue 26 Nov 2013 03:36 AM (UTC) by Nick Gammon

Message
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.


- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,133 posts)  Bio   Forum Administrator
Date Reply #1 on Tue 02 Feb 2010 08:12 PM (UTC)

Amended on Tue 02 Feb 2010 11:12 PM (UTC) by Nick Gammon

Message
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 between the lines below (to the Clipboard)
  2. Open a text editor (such as Notepad) and paste the plugin into it
  3. Save to disk on your PC, preferably in your plugins directory, as Health_Bar_Miniwindow_Telnet.xml
  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"



<?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%)


- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,133 posts)  Bio   Forum Administrator
Date Reply #2 on Tue 02 Feb 2010 08:20 PM (UTC)

Amended on Tue 02 Feb 2010 08:21 PM (UTC) by Nick Gammon

Message

Example screen shots.

Not fighting:

Fighting:

Dead:


- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,133 posts)  Bio   Forum Administrator
Date Reply #3 on Tue 02 Feb 2010 08:24 PM (UTC)

Amended on Tue 02 Feb 2010 11:13 PM (UTC) by Nick Gammon

Message
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 between the lines below (to the Clipboard)
  2. Open a text editor (such as Notepad) and paste the plugin into it
  3. Save to disk on your PC, preferably in your plugins directory, as Room_Location_Telnet.xml
  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"



<?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>

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,133 posts)  Bio   Forum Administrator
Date Reply #4 on Tue 02 Feb 2010 08:24 PM (UTC)
Message

Example screen shot.


- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,133 posts)  Bio   Forum Administrator
Date Reply #5 on Tue 02 Feb 2010 08:25 PM (UTC)

Amended on Tue 02 Feb 2010 11:22 PM (UTC) by Nick Gammon

Message
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 between the lines below (to the Clipboard)
  2. Open a text editor (such as Notepad) and paste the plugin into it
  3. Save to disk on your PC, preferably in your plugins directory, as Experience_Bar_Telnet.xml
  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"



<?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>

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,133 posts)  Bio   Forum Administrator
Date Reply #6 on Tue 02 Feb 2010 08:26 PM (UTC)
Message

Example screen shot.


- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,133 posts)  Bio   Forum Administrator
Date Reply #7 on Tue 02 Feb 2010 08:32 PM (UTC)

Amended on Tue 02 Feb 2010 08:37 PM (UTC) by Nick Gammon

Message
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 );
 }

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,133 posts)  Bio   Forum Administrator
Date Reply #8 on Tue 02 Feb 2010 09:19 PM (UTC)

Amended on Tue 02 Feb 2010 11:14 PM (UTC) by Nick Gammon

Message
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 between the lines below (to the Clipboard)
  2. Open a text editor (such as Notepad) and paste the plugin into it
  3. Save to disk on your PC, preferably in your plugins directory, as Telnet_Option_Test.xml
  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"



<?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.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,133 posts)  Bio   Forum Administrator
Date Reply #9 on Tue 02 Feb 2010 09:24 PM (UTC)
Message
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.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Worstje   Netherlands  (899 posts)  Bio
Date Reply #10 on Wed 03 Feb 2010 12:52 PM (UTC)
Message
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.)
Top

Posted by David Haley   USA  (3,881 posts)  Bio
Date Reply #11 on Wed 03 Feb 2010 02:02 PM (UTC)
Message
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?

David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone

http://david.the-haleys.org
Top

Posted by Twisol   USA  (2,257 posts)  Bio
Date Reply #12 on Wed 03 Feb 2010 04:33 PM (UTC)
Message
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.

'Soludra' on Achaea

Blog: http://jonathan.com/
GitHub: http://github.com/Twisol
Top

Posted by Nick Gammon   Australia  (23,133 posts)  Bio   Forum Administrator
Date Reply #13 on Wed 03 Feb 2010 07:16 PM (UTC)
Message
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.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,133 posts)  Bio   Forum Administrator
Date Reply #14 on Wed 03 Feb 2010 07:20 PM (UTC)
Message
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.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

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

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


742,879 views.

This is page 1, subject is 23 pages long: 1 2  3  4  5  6  7  8  9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  [Next page]

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

Go to topic:           Search the forum


[Go to top] top

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