Author Topic: Starting a mud: worthwhile or not?  (Read 4215 times)

Offline Camlorn

  • Friend
  • **
  • Posts: 76
    • View Profile
Starting a mud: worthwhile or not?
« on: September 26, 2011, 09:36:25 AM »
Hello,
    So, as some of you know on the i3 network, I'm trying to start a mud.  This post isn't an advertisement, it's a question of is it worth it?
    Someone pointed out to me that there's a critical mass of sorts with players; you won't get newbies to stay unless you have players on, but you need newbies in the first place to be those players.  I don't foresee any coding problems that would cause me to outright abandon this, but I will if my chances of getting more than a couple players are very, very low.
    Which is sad, because I have a lot of ideas, including nonstandard nonautomnated combat, and I think I could do a good job.  I'd be interested to see what your thoughts are on this, and any numbers if anyone has them for recent muds.

Offline drakkos

  • Acquaintance
  • *
  • Posts: 28
  • Work My Darkest Larks
    • View Profile
    • Epitaph Online
Re: Starting a mud: worthwhile or not?
« Reply #1 on: September 26, 2011, 12:15:37 PM »
It depends entirely on what you want out of the experience.

Most MUDs are going to end up as a 'ship in a bottle'.  They're a thing you pick at in your spare time, and after a few years you realise that you're never going to open for players.  There's nothing wrong with that - some people build elaborate model railways in their basement, others construct imaginary virtual worlds.  It's a perfectly valid way to spend your time, provided that the experience of building the world is enough for you.

If at the end of it you want players, it becomes a little trickier.  Text games have always been a hard sell, and they're a harder sell now than they've ever been.   I don't believe it's futile, but I think you need to be realistic.  Have a look at mudstats - the numbers are somewhat sobering.  Of 739 MUDs registered, less than 200 have a 30-day average of more than 10 players.   Well over that number have no players at all.   You can get players, even nowadays, but the question is - how many players are enough for you?   Realistically, you need to be thinking in terms of tens of players rather than the hundreds that the 'big MUDs' can draw in.  Is that enough?

It's not all doom and gloom though.  The pool of mudders is diminishing every year, but that doesn't mean that new ones aren't out there.  Back in the heyday of MUDs, you didn't have things like almost ubiquitous social networking on which to piggy back.  I don't believe that there is viability in mudding when everyone is still recruiting via the same MUD sites.  I think the only way a new MUD will be viable nowadays is if they can crack the puzzle of getting non-mudders to try it out and stick with it for long enough to appreciate the format.  Nobody has cracked that yet, but my belief is that it'll happen eventually.  Maybe text games will only appeal to one percent of one percent of game players, but that's actually a substantial market if you can get them to try it out long enough to become hooked.  :-P

As I say, nobody has cracked that puzzle yet, and if you feel that you *need* players for your investment of time to have been worth it, there are other ways to build games in this day and age that can achieve market viability without the extra baggage associated with text gaming.  I think you need to want to build a *MUD* for it to be worth your time, not just want to build a game.  I also think that the MUD that does crack the puzzle isn't going to be a mud as purists define it.  Personally though, I wouldn't have started Epitaph if I didn't believe that I could build a player-base around it.

Drakkos.
Epitaph Online - http://drakkos.co.uk

Offline quixadhal

  • BFF
  • ***
  • Posts: 631
    • View Profile
    • WileyMUD
Re: Starting a mud: worthwhile or not?
« Reply #2 on: September 26, 2011, 03:35:46 PM »
I have a couple of suggestions that might (or might not) help. :)

Totally aside from gameplay, interface, quality, whatever... Drakkos mentioned the idea that muds are no longer the platform of choice for social networking.

That's worth looking into.

If you were to allow people to hook text feeds from twitter/facebook/etc into in-game channels, and give them a way to post back to these things, it might both give them a reason/excuse to sit around in your MUD and socialize... AND it would perhaps get their friends to wonder what "FooMUD" is.

People love collections and achievements too.  If you make systems where you can collect bits of things and turn them in for credit and/or earn achievements for doing certain things, AND have that auto-post to their twitter/facebook/etc, it also counts as a plus for most people.

Offline Nulvect

  • BFF
  • ***
  • Posts: 127
    • View Profile
Re: Starting a mud: worthwhile or not?
« Reply #3 on: September 27, 2011, 03:09:55 AM »
If you were to allow people to hook text feeds from twitter/facebook/etc into in-game channels, and give them a way to post back to these things, it might both give them a reason/excuse to sit around in your MUD and socialize... AND it would perhaps get their friends to wonder what "FooMUD" is.

This. A million times this.

I am going to look into this myself. Now the question is - handle it with LPC sockets and write my own code from scratch, or try to interface with an external python/perl/pike/etc script that uses libraries for the specific protocols. Hmm.

Offline Camlorn

  • Friend
  • **
  • Posts: 76
    • View Profile
Re: Starting a mud: worthwhile or not?
« Reply #4 on: September 27, 2011, 09:14:46 AM »
Ok, so mostly agreeing with drakkos's model train/whatever comment; I may just puick at it for fun, in that case.  It's good practice for dealing with large systems and starting from scratch does provide a lot of insights, and no-one's used dgd persistence in an actual production game I can find; who knows, the mudlib might eventually be releasable.
    As for facebook and twitter, I didn't know there's even a protocol for that; I don't have an account on either because I can start a blog and have the same functionality if I decide I want.  Also, I'm visually impaired, and their site design drives me nuts.
    Still, it's an avenue for players if I ever get to a releasable point; does anyone know how to find out more about those protocols?

    Also, don't use an external script for interface, in my opinion.  You can use the libraries provided by i.e. python, but then you have to write your own protocol to interface with that.  And, it's another thing that can break along the way, and you'd probably never know that it wasn't working.

Offline Kaylus

  • Acquaintance
  • *
  • Posts: 8
    • View Profile
Re: Starting a mud: worthwhile or not?
« Reply #5 on: September 27, 2011, 10:11:22 AM »
Quote
If you were to allow people to hook text feeds from twitter/facebook/etc into in-game channels, and give them a way to post back to these things, it might both give them a reason/excuse to sit around in your MUD and socialize... AND it would perhaps get their friends to wonder what "FooMUD" is.

The main issue with this is attribution; if I post to a comment to the game, the following messages on the channel going into a reply to that comment might not be related, so you'd have to have directed message to the character, and this doesn't happen alot on channels. "[chat] Kaylus replies to Jezebel: Hahaha, that's the funniest thing I've heard!!!!"

Of course, it's feasible if you do make such a system, I'd be interested in looking at it. Another idea would be linking a channel into a direct twitter feed of it's own (MojoMud_Chat twitter account, or facebook "interest page" account). With the twitter account you'd have to worry about text limits, and with the facebook account it would be really great to be able to thread/take advantage of comments but you run into the same issue as above.

Quote
People love collections and achievements too.  If you make systems where you can collect bits of things and turn them in for credit and/or earn achievements for doing certain things, AND have that auto-post to their twitter/facebook/etc, it also counts as a plus for most people.

That's an excellent idea, since it's the basis of sites like PSN, XboxLive, and even Facebook and G+ gaming systems. At least that mine garner interest for the game and allow an outside competition/some kind of link to the facebook world.

Offline drakkos

  • Acquaintance
  • *
  • Posts: 28
  • Work My Darkest Larks
    • View Profile
    • Epitaph Online
Re: Starting a mud: worthwhile or not?
« Reply #6 on: September 27, 2011, 10:29:58 AM »
As a data point on how worthwhile an achievement system can be, when I added such a system to Discworld there was a very noticeable jump in player numbers (of around 40 players or so in peak time).  Suddenly we started seeing people who hadn't turned up in ages, and some of them even stayed around once they'd gotten the taste for it again.  We even broke 200 players online at once, which is something that hadn't been done since 2004ish. 

On Discworld it was successful because it gave people who had 'left' a new reason to log on again, but I think Quix and Kaylus are right that new players will largely expect systems like this from the get-go, along with the other accouterments they're familiar with from other gaming contexts.

Drakkos.
Epitaph Online - http://drakkos.co.uk

Offline drakkos

  • Acquaintance
  • *
  • Posts: 28
  • Work My Darkest Larks
    • View Profile
    • Epitaph Online
Re: Starting a mud: worthwhile or not?
« Reply #7 on: October 09, 2011, 06:03:15 AM »
On the topic of achievement systems, I wrote this today for my MUD blog:  http://drakkos.co.uk/blog/blog.c?action=filter&blog=mud%20commentary&id=619
Epitaph Online - http://drakkos.co.uk

Offline amylase

  • Friend
  • **
  • Posts: 75
    • View Profile
    • gpLand
Re: Starting a mud: worthwhile or not?
« Reply #8 on: May 12, 2013, 12:12:11 AM »
If you were to allow people to hook text feeds from twitter/facebook/etc into in-game channels, and give them a way to post back to these things, it might both give them a reason/excuse to sit around in your MUD and socialize... AND it would perhaps get their friends to wonder what "FooMUD" is.

This. A million times this.

I am going to look into this myself. Now the question is - handle it with LPC sockets and write my own code from scratch, or try to interface with an external python/perl/pike/etc script that uses libraries for the specific protocols. Hmm.

Brilliant idea. I've been waiting for 2 years for someone to echo this idea but never quite had the time to look further into it myself.  Include IRC in that list too. IRC has been around forever and will continue to exist. The other social media come and go.

Here are old codes from Tricky for MUD <-> LPC bridge. Tried contacting him for a port but he seemed busy too.
http://lpmuds.net/forum/index.php?topic=651.0

Code: [Select]
/* socket_d.c
 *
 * Tricky @ Rock the Halo
 * 5-JAN-2007
 * Socket daemon
 *
 * Handles MudMode, Stream and Datagram socket connections.
 */

/* Socket types and errors */
#include <socket.h>

/* Set this to a name you know will not be used as an ID */
#define SOCKET_ID "[SOCKET_D]"

/* Lower this if you want less write retries */
#define WRITERETRYLIMIT 20

/* Private vars */
privatev mapping Sockets, Ids;

/* Private funcs */
privatef void close_callback(int);
privatef void internal_close(int);
privatef void listen_callback(int);
privatef void client_read_callback(int, mixed);
privatef void udp_client_read_callback(int, mixed, string);
privatef void server_read_callback(int, mixed);
privatef void udp_server_read_callback(int, mixed, string);
privatef void write_callback(int);
privatef void internal_write(int, mixed);
privatef void log(string, string, string);
privatef void error_log(string, string, int);

/* Public funcs */
varargs int client_create(string, string, int, mapping);
varargs int server_create(string, int, mapping);
varargs void close(mixed, int);
void client_write(mixed, mixed);
void server_write(mixed, mixed, int);
void udp_write(mixed, mixed, string);
mapping query_socket_info();

/* Function name: create
 * Description:   Initialise the object data.
 */
void create()
{
  Sockets = ([
    SOCKET_ID: ([
      "logfile": "socket"
    ])
  ]);

  Ids = ([ ]);

  set_heart_beat(20 / __HEARTBEAT_INTERVAL__);
  log(SOCKET_ID, "Success", "Created.");
}

/* Function name: remove
 * Description:   Closes all sockets and destructs itself.
 */
void remove()
{
  log(SOCKET_ID, "Notice", "Removing all sockets.");

  foreach (string sockID, mapping sock in Sockets)
  {
    if (sockID == SOCKET_ID) continue;

    log(sockID, "Notice", "Removing socket.");
    internal_close(sock["fd"]);
  }

  log(SOCKET_ID, "Warning", "Destructing.");
  destruct();
}

/* Function name: heart_beat
 * Description:   Cleans up orphaned file descriptors.
 */
void heart_beat()
{
  foreach (string sockID, mapping sock in Sockets)
  {
    if (sockID == SOCKET_ID) continue;

    if (objectp(sock["owner"]))
    {
      /* Persistent sockets */
      if (sock["persistent"]) continue;

      /* Non-persistent sockets */
      if (time() - sock["time"] < 3 * 60) continue;
    }

    log(sockID, "Notice", "Removing orphaned socket '" + Ids[sock["fd"]] + "' (" + sock["fd"] + ")");
    internal_close(sock["fd"]);
  }
}

/* Function name: close_callback
 * Description:   Called when the connection terminates unexpectably.
 * Arguments:     fd - Socket file descriptor.
 */
privatef void close_callback(int fd)
{
  mapping sock;
  object  o;
  string  f;
  string  sockID, type;

  if (member_array(fd, keys(Ids)) == -1) return;

  sockID = Ids[fd];
  sock = copy(Sockets[sockID]);
  type = sock["type"];
  log(sockID, "Warning", type + " connection (" + fd + ") terminated.");

  /* Indicate that the socket is closed */
  sock["closed"] = 1;

  o = sock["owner"];
  f = sock["closef"];

  /* Call the user function if one is set up */
  if (stringp(f)) call_other(o, f, fd);

  /* Remove the socket data */
  map_delete(Sockets, sockID);
  map_delete(Ids, fd);
}

/* Function name: internal_close
 * Description:   Internal socket close.
 * Arguments:     fd - Socket file descriptor.
 */
privatef void internal_close(int fd)
{
  mapping sock;
  string  sockID;
  int     sock_return;

  if (member_array(fd, keys(Ids)) == -1) return;

  sockID = Ids[fd];
  sock = copy(Sockets[sockID]);

  if (!mapp(sock))
  {
    /* Remove the socket data */
    map_delete(Sockets, sockID);
    map_delete(Ids, fd);

    return;
  }

  log(SOCKET_ID, "Warning", sock["type"] + " connection (" + sockID + "/" + sock["fd"] + ") closing.");

  if (!sock["closed"] && sock["buffer"])
  {
    call_out("internal_close", 2, fd);

    return;
  }

  if ((!sock["closed"] || sock["closing"]) && ((sock_return = socket_close(sock["fd"])) != EESUCCESS))
    error_log(sockID, sock["type"] + "/internal_close", sock_return);
  else
    log(sockID, "Success", sock["type"] + " connection (" + sock["fd"] + ") closed.");

  /* Remove the socket data */
  map_delete(Sockets, sockID);
  map_delete(Ids, fd);
}

/* Function name: listen_callback
 * Description:   Called when the socket receives an incoming connection.
 * Arguments:     fd - Socket file descriptor.
 */
privatef void listen_callback(int fd)
{
  object  o;
  mapping listenSock, sock;
  string  sockID, incomingSockID, f;
  int     sock_return;

  sockID = Ids[fd];
  listenSock = copy(Sockets[sockID]);

  /* Accept the incoming connection */
  sock_return = socket_accept(listenSock["fd"], "server_read_callback", "write_callback");

  /* Couldn't accept the incoming connection */
  if (sock_return < 0)
  {
    error_log(sockID, "Listen/socket_accept", sock_return);
    internal_close(fd);

    return;
  }

  /* Initialise remote socket data */
  sock = ([
    "fd": sock_return,
    "owner": listenSock["owner"],
    "type": "Remote",
    "blocking": 0,
    "buffer": 0,
    "closing": 0,
    "closed": 0,
    "time": time(),
    "persistent": 1,
    "socketType": listenSock["socketType"],
    "readf": listenSock["readf"],
    "closef": listenSock["closef"],
    "logfile": listenSock["logfile"],
  ]);

  incomingSockID = sockID + "." + sock["fd"];
  Sockets[incomingSockID] = copy(sock);
  Ids[sock["fd"]] = incomingSockID;
  log(sockID, "Success", "Accepted the incoming connection from " + socket_address(sock["fd"]));

  o = listenSock["owner"];
  f = listenSock["listenf"];

  /* Call the user function if one is set up otherwise do nothing */
  if (stringp(f)) call_other(o, f, fd, sock["fd"]);
}

/* Function name: client_read_callback
 * Description:   Called when data arrives on the client socket.
 * Arguments:     fd - Socket file descriptor.
 *                data - Incoming data.
 */
privatef void client_read_callback(int fd, mixed data)
{
  string sockID = Ids[fd];
  object o = Sockets[sockID]["owner"];
  string f = Sockets[sockID]["readf"];

  /* Call the user function if one is set up otherwise do nothing */
  if (stringp(f)) call_other(o, f, fd, data);
}

/* Function name: udp_client_read_callback
 * Description:   Called when data arrives on the UDP client socket.
 * Arguments:     fd - Socket file descriptor.
 *                data - Incoming data.
 *                addr - Address ("IP PORT") of the incoming data.
 */
privatef void udp_client_read_callback(int fd, mixed data, string addr)
{
  string sockID = Ids[fd];
  object o = Sockets[sockID]["owner"];
  string f = Sockets[sockID]["readf"];

  /* Call the user function if one is set up otherwise do nothing */
  if (stringp(f)) call_other(o, f, fd, data, addr);
}

/* Function name: server_read_callback
 * Description:   Called when data arrives on the server socket.
 * Arguments:     fd - Socket file descriptor.
 *                data - Incoming data.
 */
privatef void server_read_callback(int fd, mixed data)
{
  string sockID = Ids[fd];
  object o = Sockets[sockID]["owner"];
  string f = Sockets[sockID]["readf"];

  /* Call the user function if one is set up otherwise do nothing */
  if (stringp(f)) call_other(o, f, fd, data);
}

/* Function name: udp_server_read_callback
 * Description:   Called when data arrives on the UDP server socket.
 * Arguments:     fd - Socket file descriptor.
 *                data - Incoming data.
 *                addr - Address ("IP PORT") of the incoming data.
 */
privatef void udp_server_read_callback(int fd, mixed data, string addr)
{
  string sockID = Ids[fd];
  object o = Sockets[sockID]["owner"];
  string f = Sockets[sockID]["readf"];

  /* Call the user function if one is set up otherwise do nothing */
  if (stringp(f)) call_other(o, f, fd, data, addr);
}

/* Function name: write_callback
 * Description:   Called when data is ready to be written to the socket.
 * Arguments:     fd - Socket file descriptor.
 */
privatef void write_callback(int fd)
{
  string sockID;
  int    sock_return, times;

  if (member_array(fd, keys(Ids)) == -1) return;

  sockID = Ids[fd];

  if (member_array(sockID, keys(Sockets)) == -1 && !Sockets[sockID]) return;

  /* Not blocking at the moment */
  Sockets[sockID]["blocking"] = 0;

  /* If the socket is closed (close_callback called) then clean up */
  /* If we are closing and there is no data in the buffer then close the socket */
  if (Sockets[sockID]["closed"] || (!Sockets[sockID]["buffer"] && Sockets[sockID]["closing"]))
  {
    internal_close(fd);

    return;
  }

  /* Stop trying to write data for a bit */
  if (Sockets[sockID]["writeRetry"] == WRITERETRYLIMIT)
  {
    Sockets[sockID]["blocking"] = 1;
    Sockets[sockID]["writeRetry"] = 0;
    call_out("write_callback", 5, fd);

    return;
  }

  /* Pre-set the socket_write error to EESUCCESS */
  sock_return = EESUCCESS;

  times = 10;

  /* Process when:
   * there is data in the buffer
   * socket_write is successful
   * looped less than 10 times (limit spam)
   */
  while (Sockets[sockID]["buffer"] && sock_return == EESUCCESS && times > 0)
  {
    times--;
#if 0
    /* Turn this on for lots of log messages. */
    log(
      SOCKET_ID,
      "Notice",
      sprintf("Writing on %s/%d: %O",
      sockID,
      Sockets[sockID]["fd"],
      Sockets[sockID]["buffer"][0]
      )
    );
#endif

    if (   Sockets[sockID]["socketType"] == DATAGRAM
        || Sockets[sockID]["socketType"] == DATAGRAM_BINARY)
      sock_return = socket_write(fd, Sockets[sockID]["buffer"][0], Sockets[sockID]["hostport"]);
    else
      sock_return = socket_write(fd, Sockets[sockID]["buffer"][0]);

    /* Write first set of data in buffer to the socket */
    switch (sock_return)
    {
      /* Break out of the switch if successful */
      case EESUCCESS: break;

      /* Data has been buffered and we need to suspend further writes until it is cleared */
      case EECALLBACK:
      {
        Sockets[sockID]["blocking"] = 1;

        break;
      }

      /* The socket is blocked so we need to call write_callback again */
      case EEWOULDBLOCK:
      {
        call_out("write_callback", 1, fd);

        return;
      }

      /* Problem with send */
      case EESEND:
      {
        if (!Sockets[sockID]) return;
        if (member_array("writeRetry", keys(Sockets[sockID])) == -1)
          Sockets[sockID]["writeRetry"] = 0;

        Sockets[sockID]["writeRetry"] = Sockets[sockID]["writeRetry"] + 1;
        call_out("write_callback", 2, fd);

        return;
      }

      /* Flow control has been violated, shouldn't see this */
      case EEALREADY:
      {
        Sockets[sockID]["blocking"] = 1;

        return;
      }

      /* Something went really wrong so we close the socket */
      default:
      {
        error_log(sockID, Sockets[sockID]["type"] + "/socket_write", sock_return);
        internal_close(fd);

        return;
      }

    }

    Sockets[sockID]["writeRetry"] = 0;

    /* Remove the data we have written from the buffer */
    if (sizeof(Sockets[sockID]["buffer"]) == 1)
    {
      Sockets[sockID]["buffer"] = 0;

      /* If the socket is closed (close_callback called) then clean up */
      /* or if we are closing then close the socket */
      if (Sockets[sockID]["closed"] || Sockets[sockID]["closing"])
      {
        internal_close(fd);

        return;
      }
    }
    else Sockets[sockID]["buffer"] = Sockets[sockID]["buffer"][1..<1];
  }
}

/* Function name: internal_write
 * Description:   Internal socket write.
 * Arguments:     fd - Socket file descriptor.
 *                data - Outgoing data.
 */
privatef void internal_write(int fd, mixed data)
{
  string sockID;

  sockID = Ids[fd];

  /* Add data to the buffer */
  if (Sockets[sockID]["buffer"]) Sockets[sockID]["buffer"] += ({ data });
  else Sockets[sockID]["buffer"] = ({ data });

  /* If we are blocking then return otherwise write it */
  if (Sockets[sockID]["blocking"]) return;
  else
  {
    Sockets[sockID]["writeRetry"] = 0;
    write_callback(fd);
  }
}

/* Function name: log
 * Description:   Internal socket log.
 * Arguments:     sockID - Socket ID.
 *                pre - Pre-log message.
 *                str - Log message.
 */
privatef void log(string sockID, string pre, string str)
{
  /* If we have set a log file then append the message to it */
  if (member_array(sockID, keys(Sockets)) != -1)
    log_file(
      Sockets[sockID]["logfile"],
      sprintf("%s [SOCKET_D%s]: %s\n",
      pre,
      (sockID == SOCKET_ID ? "" : "/" + sockID),
      str
      )
    );
}

/* Function name: error_log
 * Description:   Internal socket error log.
 * Arguments:     sockID - Socket ID.
 *                str - Log message.
 *                x - Socket error code.
 */
privatef void error_log(string sockID, string str, int x)
{
  log(sockID, "Error", sprintf("%s - %s", str, socket_error(x)));
}

/* Function name: client_create
 * Description:   Create a socket for clients and connects it to host IP and port.
 * Arguments:     sockID - Unique ID that can be used to reference the socket.
 *                host - Host IP to connect to.
 *                port - Host port number.
 *
 *                Optional arguments:-
 *                extra - mapping of extra arguments:-
 *                  type - Socket Type.
 *                  persistent - Keeps the connection open at all times.
 *                  readf - User defined read callback function.
 *                  closef - User defined close callback function.
 *                  releasef - User defined release callback function.
 *                  logfile - Per-socket log file.
 *
 * Return:        Socket creation success or error code.
 */
varargs int client_create(string sockID, string host, int port, mapping extra)
{
  mapping sock;
  string  readf = 0, closef = 0, releasef = 0, logfile = 0;
  int     sock_return, persistent = 0, type = -1;

  if (undefinedp(sockID) || !stringp(sockID)) return EESOCKET;
  if (undefinedp(host) || !stringp(host)) return EESOCKET;
  if (undefinedp(port) || !intp(port)) return EESOCKET;
  if (member_array(sockID, keys(Sockets)) != -1) return EEISCONN;

  if (!undefinedp(extra))
  {
    if (!undefinedp(extra["type"])) type = extra["type"];
    if (!undefinedp(extra["persistent"])) persistent = extra["persistent"];
    if (!undefinedp(extra["readf"])) readf = extra["readf"];
    if (!undefinedp(extra["closef"])) closef = extra["closef"];
    if (!undefinedp(extra["releasef"])) releasef = extra["releasef"];
    if (!undefinedp(extra["logfile"])) logfile = extra["logfile"];
  }

  /* If we haven't set a socket type then set it to STREAM */
  if (type == -1) type = STREAM;

  /* Initialise client socket data */
  sock = ([
    "fd": -1,
    "owner": previous_object(),
    "type": "Client",
    "blocking": 0,
    "writeRetry": 0,
    "closing": 0,
    "closed": 0,
    "time": time(),
    "socketType": type,
    "persistent": persistent,
    "readf": readf,
    "closef": closef,
    "releasef": releasef,
    "logfile": logfile,
  ]);

  Sockets[sockID] = copy(sock);

  /* Create a socket */
  if (type == DATAGRAM || type == DATAGRAM_BINARY)
    sock_return = socket_create(type, "udp_client_read_callback");
  else
    sock_return = socket_create(type, "client_read_callback", "close_callback");

  /* Couldn't create a socket */
  if (sock_return < 0)
  {
    error_log(sockID, "Client/socket_create", sock_return);
    map_delete(Sockets, sockID);

    return sock_return;
  }

  /* Socket created and return value is a file descriptor we use */
  sock["fd"] = sock_return;
  Ids[sock["fd"]] = sockID;
  log(sockID, "Success", "Created client socket (" + sock["fd"] + ")");

  if (type != DATAGRAM && type != DATAGRAM_BINARY)
  {
    /* Bind the socket to a system selected port */
    /* Binding isn't necessary for clients since the system *should* do it for you */
    sock_return = socket_bind(sock["fd"], 0);

    /* Couldn't bind it */
    if (sock_return < 0)
    {
      error_log(sockID, "Client/socket_bind", sock_return);
      internal_close(sock["fd"]);

      return sock_return;
    }

    log(sockID, "Success", "Client socket bound to a port.");

    /* Now we actually connect the socket to a host and port */
    sock_return = socket_connect(sock["fd"], host + " " + port, "client_read_callback", "write_callback");

    /* Couldn't connect to remote machine */
    if (sock_return < 0)
    {
      error_log(sockID, "Client/socket_connect", sock_return);
      internal_close(sock["fd"]);

      return sock_return;
    }

    log(sockID, "Success", "Connected client to " + host + " " + port);
  }

  Sockets[sockID] = copy(sock);
  log(
    SOCKET_ID,
    "Notice",
    sprintf("Client created - %s/%d: %O", sockID, sock["fd"], Sockets[sockID])
  );

  /* Call the user function if one is set up otherwise do nothing */
  if (stringp(sock["releasef"]))
  {
    sock_return = socket_release(sock["fd"], sock["owner"], sock["releasef"]);

    if (sock_return != EESUCCESS)
    {
      error_log(sockID, "Client/socket_release", sock_return);

      return sock_return;
    }
  }

  /* Return the file descriptor on success */
  return sock["fd"];
}

/* Function name: server_create
 * Description:   Create a socket for a server and bind it to a port.
 * Arguments:     sockID - Unique ID that can be used to reference the socket.
 *                port - Server port number.
 *
 *                Optional arguments:-
 *                extra - mapping of extra arguments:-
 *                  type - Socket Type.
 *                  readf - User defined read callback function.
 *                  closef - User defined close callback function.
 *                  listenf - User defined listen callback function.
 *                  releasef - User defined release callback function.
 *                  logfile - Per-socket log file.
 *
 * Return:        Socket creation success or error code.
 */
varargs int server_create(string sockID, int port, mapping extra)
{
  mapping sock;
  string  readf = 0, closef = 0, listenf = 0, releasef = 0, logfile = 0;
  int     sock_return, type = -1;

  if (undefinedp(sockID) || !stringp(sockID)) return EESOCKET;
  if (undefinedp(port) || !intp(port)) return EESOCKET;
  if (member_array(sockID, keys(Sockets)) != -1) return EEISCONN;

  if (!undefinedp(extra))
  {
    if (!undefinedp(extra["type"])) type = extra["type"];
    if (!undefinedp(extra["readf"])) readf = extra["readf"];
    if (!undefinedp(extra["closef"])) closef = extra["closef"];
    if (!undefinedp(extra["listenf"])) listenf = extra["listenf"];
    if (!undefinedp(extra["releasef"])) releasef = extra["releasef"];
    if (!undefinedp(extra["logfile"])) logfile = extra["logfile"];
  }

  /* If we haven't set a socket type then set it to STREAM */
  if (type == -1) type = STREAM;

  /* Initialise server socket data */
  sock = ([
    "fd": -1,
    "owner": previous_object(),
    "type": "Server",
    "blocking": 0,
    "writeRetry": 0,
    "buffer": 0,
    "closing": 0,
    "closed": 0,
    "time": time(),
    "persistent": 1,
    "socketType": type,
    "readf": readf,
    "closef": closef,
    "listenf": listenf,
    "releasef": releasef,
    "logfile": logfile,
  ]);

  Sockets[sockID] = copy(sock);

  /* Create a socket */
  if (type == DATAGRAM || type == DATAGRAM_BINARY)
    sock_return = socket_create(type, "udp_server_read_callback");
  else
    sock_return = socket_create(type, "server_read_callback", "close_callback");

  /* Couldn't create a socket */
  if (sock_return < 0)
  {
    error_log(sockID, "Server/socket_create", sock_return);
    map_delete(Sockets, sockID);

    return sock_return;
  }

  /* Socket created and return value is a file descriptor we use */
  sock["fd"] = sock_return;
  Ids[sock["fd"]] = sockID;
  log(sockID, "Success", "Created server socket (" + sock["fd"] + ")");

  /* Bind the socket to the supplied port */
  sock_return = socket_bind(sock["fd"], port);

  /* Couldn't bind it */
  if (sock_return < 0)
  {
    error_log(sockID, "Server/socket_bind", sock_return);
    internal_close(sock["fd"]);

    return sock_return;
  }

  log(sockID, "Success", "Server socket bound to port " + port);

  if (type != DATAGRAM && type != DATAGRAM_BINARY)
  {
    /* Now we set up the listen callback */
    sock_return = socket_listen(sock["fd"], "listen_callback");

    /* Couldn't set up the socket to listen */
    if (sock_return < 0)
    {
      error_log(sockID, "Server/socket_listen", sock_return);
      internal_close(sock["fd"]);

      return sock_return;
    }

    log(sockID, "Success", "Server listen callback set up.");
  }

  Sockets[sockID] = copy(sock);
  log(
    SOCKET_ID,
    "Notice",
    sprintf("Server created - %s/%d: %O", sockID, sock["fd"], Sockets[sockID])
  );

  /* Call the user function if one is set up otherwise do nothing */
  if (stringp(sock["releasef"]))
  {
    sock_return = socket_release(sock["fd"], sock["owner"], sock["releasef"]);

    if (sock_return != EESUCCESS)
    {
      error_log(sockID, "Server/socket_release", sock_return);

      return sock_return;
    }
  }

  /* Return the file descriptor on success */
  return sock["fd"];
}

/* Function name: close
 * Description:   Public socket close.
 * Arguments:     fd - Socket file descriptor or ID.
 *
 *                Optional argument:-
 *                delay - Delays the closing of the socket for 'delay' seconds.
 */
varargs void close(mixed fd, int delay)
{
  if (undefinedp(delay)) delay = 0;

  if (intp(fd))
  {
    if (member_array(fd, keys(Ids)) == -1) return;
    if (Sockets[Ids[fd]]["owner"] != previous_object())
    {
      error_log(Ids[fd], "close/sockID '" + Ids[fd] + "' in object '" + Sockets[Ids[fd]]["owner"], EESECURITY);

      return;
    }

    call_out("internal_close", delay, fd);
  }
  else
  if (stringp(fd))
  {
    if (member_array(fd, keys(Sockets)) == -1) return;
    if (Sockets[fd]["owner"] != previous_object())
    {
      error_log(fd, "close/sockID '" + Ids[fd] + "' in object '" + Sockets[fd]["owner"], EESECURITY);

      return;
    }

    call_out("internal_close", delay, Sockets[fd]["fd"]);
  }
}

/* Function name: client_write
 * Description:   Public client socket write.
 * Arguments:     fd - Socket file descriptor.
 *                data - Outgoing data.
 */
void client_write(mixed fd, mixed data)
{
  if (intp(fd))
  {
    if (member_array(fd, keys(Ids)) == -1) return;
    if (Sockets[Ids[fd]]["owner"] != previous_object())
    {
      error_log(Ids[fd], "client_write/sockID '" + Ids[fd] + "' in object '" + Sockets[Ids[fd]]["owner"], EESECURITY);

      return;
    }

    if (   Sockets[Ids[fd]]["socketType"] == DATAGRAM
        || Sockets[Ids[fd]]["socketType"] == DATAGRAM_BINARY)
    {
      error_log(Ids[fd], "client_write/sockID '" + Ids[fd] + "' in object '" + Sockets[Ids[fd]]["owner"], EETYPENOTSUPP);

      return;
    }

    internal_write(fd, data);
  }
  else
  if (stringp(fd))
  {
    if (member_array(fd, keys(Sockets)) == -1) return;
    if (Sockets[fd]["owner"] != previous_object())
    {
      error_log(fd, "client_write/sockID '" + Ids[fd] + "' in object '" + Sockets[fd]["owner"], EESECURITY);

      return;
    }

    if (   Sockets[fd]["socketType"] == DATAGRAM
        || Sockets[fd]["socketType"] == DATAGRAM_BINARY)
    {
      error_log(fd, "client_write/sockID '" + fd + "' in object '" + Sockets[fd]["owner"], EETYPENOTSUPP);

      return;
    }

    internal_write(Sockets[fd]["fd"], data);
  }
}

/* Function name: server_write
 * Description:   Public server socket write.
 * Arguments:     fd - Socket file descriptor.
 *                data - Outgoing data.
 *                close - If set to '0' the connection is kept open after writing the data.
 *                If set to '1' the connection is closed after writing the data.
 */
void server_write(mixed fd, mixed data, int close)
{
  if (undefinedp(close)) close = 0;

  if (intp(fd))
  {
    if (member_array(fd, keys(Ids)) == -1) return;
    if (Sockets[Ids[fd]]["owner"] != previous_object())
    {
      error_log(Ids[fd], "server_write/sockID '" + Ids[fd] + "' in object '" + Sockets[Ids[fd]]["owner"], EESECURITY);

      return;
    }

    if (   Sockets[Ids[fd]]["socketType"] == DATAGRAM
        || Sockets[Ids[fd]]["socketType"] == DATAGRAM_BINARY)
    {
      error_log(Ids[fd], "server_write/sockID '" + Ids[fd] + "' in object '" + Sockets[Ids[fd]]["owner"], EETYPENOTSUPP);

      return;
    }

    Sockets[Ids[fd]]["closing"] = close;
    internal_write(fd, data);
  }
  else
  if (stringp(fd))
  {
    if (member_array(fd, keys(Sockets)) == -1) return;
    if (Sockets[fd]["owner"] != previous_object())
    {
      error_log(fd, "server_write/sockID '" + Ids[fd] + "' in object '" + Sockets[fd]["owner"], EESECURITY);

      return;
    }

    if (   Sockets[fd]["socketType"] == DATAGRAM
        || Sockets[fd]["socketType"] == DATAGRAM_BINARY)
    {
      error_log(fd, "server_write/sockID '" + fd + "' in object '" + Sockets[fd]["owner"], EETYPENOTSUPP);

      return;
    }

    Sockets[fd]["closing"] = close;
    internal_write(Sockets[fd]["fd"], data);
  }
}

/* Function name: udp_write
 * Description:   Public UDP socket write.
 * Arguments:     fd - Socket file descriptor.
 *                data - Outgoing data.
 *                addr - Address ("IP PORT") of the outgoing data.
 */
void udp_write(mixed fd, mixed data, string addr)
{
  if (intp(fd))
  {
    if (member_array(fd, keys(Ids)) == -1) return;
    if (Sockets[Ids[fd]]["owner"] != previous_object())
    {
      error_log(Ids[fd], "udp_write/sockID '" + Ids[fd] + "' in object '" + Sockets[Ids[fd]]["owner"], EESECURITY);

      return;
    }

    if (   Sockets[Ids[fd]]["socketType"] != DATAGRAM
        && Sockets[Ids[fd]]["socketType"] != DATAGRAM_BINARY)
    {
      error_log(Ids[fd], "udp_write/sockID '" + Ids[fd] + "' in object '" + Sockets[Ids[fd]]["owner"], EETYPENOTSUPP);

      return;
    }

    Sockets[Ids[fd]]["hostport"] = addr;
    internal_write(fd, data);
  }
  else
  if (stringp(fd))
  {
    if (member_array(fd, keys(Sockets)) == -1) return;
    if (Sockets[fd]["owner"] != previous_object())
    {
      error_log(fd, "udp_write/sockID '" + Ids[fd] + "' in object '" + Sockets[fd]["owner"], EESECURITY);

      return;
    }

    if (   Sockets[fd]["socketType"] != DATAGRAM
        && Sockets[fd]["socketType"] != DATAGRAM_BINARY)
    {
      error_log(fd, "udp_write/sockID '" + fd + "' in object '" + Sockets[fd]["owner"], EETYPENOTSUPP);

      return;
    }

    Sockets[fd]["hostport"] = addr;
    internal_write(Sockets[fd]["fd"], data);
  }
}

/* Function name: query_socket_info
 * Description:   Queries the internal Sockets mapping.
 * Return:        All connected sockets.
 */
mapping query_socket_info()
{
  mapping tmp = ([ ]);

  foreach (string sockID, mapping sock in Sockets)
  {
    if (sockID == SOCKET_ID) continue;

    tmp[sockID] = sock;
  }

  return copy(tmp);
}
« Last Edit: May 12, 2013, 12:18:10 AM by amylase »

Offline amylase

  • Friend
  • **
  • Posts: 75
    • View Profile
    • gpLand
Re: Starting a mud: worthwhile or not?
« Reply #9 on: May 12, 2013, 12:18:44 AM »

Another piece here:

Code: [Select]
/* socket_d.c
 *
 * Tricky @ Rock the Halo
 * 5-JAN-2007
 * Socket daemon
 *
 * Handles MudMode, Stream and Datagram socket connections.
 */

/* Socket types and errors */
#include <socket.h>

/* Set this to a name you know will not be used as an ID */
#define SOCKET_ID "[SOCKET_D]"

/* Lower this if you want less write retries */
#define WRITERETRYLIMIT 20

/* Private vars */
privatev mapping Sockets, Ids;

/* Private funcs */
privatef void close_callback(int);
privatef void internal_close(int);
privatef void listen_callback(int);
privatef void client_read_callback(int, mixed);
privatef void udp_client_read_callback(int, mixed, string);
privatef void server_read_callback(int, mixed);
privatef void udp_server_read_callback(int, mixed, string);
privatef void write_callback(int);
privatef void internal_write(int, mixed);
privatef void log(string, string, string);
privatef void error_log(string, string, int);

/* Public funcs */
varargs int client_create(string, string, int, mapping);
varargs int server_create(string, int, mapping);
varargs void close(mixed, int);
void client_write(mixed, mixed);
void server_write(mixed, mixed, int);
void udp_write(mixed, mixed, string);
mapping query_socket_info();

/* Function name: create
 * Description:   Initialise the object data.
 */
void create()
{
  Sockets = ([
    SOCKET_ID: ([
      "logfile": "socket"
    ])
  ]);

  Ids = ([ ]);

  set_heart_beat(20 / __HEARTBEAT_INTERVAL__);
  log(SOCKET_ID, "Success", "Created.");
}

/* Function name: remove
 * Description:   Closes all sockets and destructs itself.
 */
void remove()
{
  log(SOCKET_ID, "Notice", "Removing all sockets.");

  foreach (string sockID, mapping sock in Sockets)
  {
    if (sockID == SOCKET_ID) continue;

    log(sockID, "Notice", "Removing socket.");
    internal_close(sock["fd"]);
  }

  log(SOCKET_ID, "Warning", "Destructing.");
  destruct();
}

/* Function name: heart_beat
 * Description:   Cleans up orphaned file descriptors.
 */
void heart_beat()
{
  foreach (string sockID, mapping sock in Sockets)
  {
    if (sockID == SOCKET_ID) continue;

    if (objectp(sock["owner"]))
    {
      /* Persistent sockets */
      if (sock["persistent"]) continue;

      /* Non-persistent sockets */
      if (time() - sock["time"] < 3 * 60) continue;
    }

    log(sockID, "Notice", "Removing orphaned socket '" + Ids[sock["fd"]] + "' (" + sock["fd"] + ")");
    internal_close(sock["fd"]);
  }
}

/* Function name: close_callback
 * Description:   Called when the connection terminates unexpectably.
 * Arguments:     fd - Socket file descriptor.
 */
privatef void close_callback(int fd)
{
  mapping sock;
  object  o;
  string  f;
  string  sockID, type;

  if (member_array(fd, keys(Ids)) == -1) return;

  sockID = Ids[fd];
  sock = copy(Sockets[sockID]);
  type = sock["type"];
  log(sockID, "Warning", type + " connection (" + fd + ") terminated.");

  /* Indicate that the socket is closed */
  sock["closed"] = 1;

  o = sock["owner"];
  f = sock["closef"];

  /* Call the user function if one is set up */
  if (stringp(f)) call_other(o, f, fd);

  /* Remove the socket data */
  map_delete(Sockets, sockID);
  map_delete(Ids, fd);
}

/* Function name: internal_close
 * Description:   Internal socket close.
 * Arguments:     fd - Socket file descriptor.
 */
privatef void internal_close(int fd)
{
  mapping sock;
  string  sockID;
  int     sock_return;

  if (member_array(fd, keys(Ids)) == -1) return;

  sockID = Ids[fd];
  sock = copy(Sockets[sockID]);

  if (!mapp(sock))
  {
    /* Remove the socket data */
    map_delete(Sockets, sockID);
    map_delete(Ids, fd);

    return;
  }

  log(SOCKET_ID, "Warning", sock["type"] + " connection (" + sockID + "/" + sock["fd"] + ") closing.");

  if (!sock["closed"] && sock["buffer"])
  {
    call_out("internal_close", 2, fd);

    return;
  }

  if ((!sock["closed"] || sock["closing"]) && ((sock_return = socket_close(sock["fd"])) != EESUCCESS))
    error_log(sockID, sock["type"] + "/internal_close", sock_return);
  else
    log(sockID, "Success", sock["type"] + " connection (" + sock["fd"] + ") closed.");

  /* Remove the socket data */
  map_delete(Sockets, sockID);
  map_delete(Ids, fd);
}

/* Function name: listen_callback
 * Description:   Called when the socket receives an incoming connection.
 * Arguments:     fd - Socket file descriptor.
 */
privatef void listen_callback(int fd)
{
  object  o;
  mapping listenSock, sock;
  string  sockID, incomingSockID, f;
  int     sock_return;

  sockID = Ids[fd];
  listenSock = copy(Sockets[sockID]);

  /* Accept the incoming connection */
  sock_return = socket_accept(listenSock["fd"], "server_read_callback", "write_callback");

  /* Couldn't accept the incoming connection */
  if (sock_return < 0)
  {
    error_log(sockID, "Listen/socket_accept", sock_return);
    internal_close(fd);

    return;
  }

  /* Initialise remote socket data */
  sock = ([
    "fd": sock_return,
    "owner": listenSock["owner"],
    "type": "Remote",
    "blocking": 0,
    "buffer": 0,
    "closing": 0,
    "closed": 0,
    "time": time(),
    "persistent": 1,
    "socketType": listenSock["socketType"],
    "readf": listenSock["readf"],
    "closef": listenSock["closef"],
    "logfile": listenSock["logfile"],
  ]);

  incomingSockID = sockID + "." + sock["fd"];
  Sockets[incomingSockID] = copy(sock);
  Ids[sock["fd"]] = incomingSockID;
  log(sockID, "Success", "Accepted the incoming connection from " + socket_address(sock["fd"]));

  o = listenSock["owner"];
  f = listenSock["listenf"];

  /* Call the user function if one is set up otherwise do nothing */
  if (stringp(f)) call_other(o, f, fd, sock["fd"]);
}

/* Function name: client_read_callback
 * Description:   Called when data arrives on the client socket.
 * Arguments:     fd - Socket file descriptor.
 *                data - Incoming data.
 */
privatef void client_read_callback(int fd, mixed data)
{
  string sockID = Ids[fd];
  object o = Sockets[sockID]["owner"];
  string f = Sockets[sockID]["readf"];

  /* Call the user function if one is set up otherwise do nothing */
  if (stringp(f)) call_other(o, f, fd, data);
}

/* Function name: udp_client_read_callback
 * Description:   Called when data arrives on the UDP client socket.
 * Arguments:     fd - Socket file descriptor.
 *                data - Incoming data.
 *                addr - Address ("IP PORT") of the incoming data.
 */
privatef void udp_client_read_callback(int fd, mixed data, string addr)
{
  string sockID = Ids[fd];
  object o = Sockets[sockID]["owner"];
  string f = Sockets[sockID]["readf"];

  /* Call the user function if one is set up otherwise do nothing */
  if (stringp(f)) call_other(o, f, fd, data, addr);
}

/* Function name: server_read_callback
 * Description:   Called when data arrives on the server socket.
 * Arguments:     fd - Socket file descriptor.
 *                data - Incoming data.
 */
privatef void server_read_callback(int fd, mixed data)
{
  string sockID = Ids[fd];
  object o = Sockets[sockID]["owner"];
  string f = Sockets[sockID]["readf"];

  /* Call the user function if one is set up otherwise do nothing */
  if (stringp(f)) call_other(o, f, fd, data);
}

/* Function name: udp_server_read_callback
 * Description:   Called when data arrives on the UDP server socket.
 * Arguments:     fd - Socket file descriptor.
 *                data - Incoming data.
 *                addr - Address ("IP PORT") of the incoming data.
 */
privatef void udp_server_read_callback(int fd, mixed data, string addr)
{
  string sockID = Ids[fd];
  object o = Sockets[sockID]["owner"];
  string f = Sockets[sockID]["readf"];

  /* Call the user function if one is set up otherwise do nothing */
  if (stringp(f)) call_other(o, f, fd, data, addr);
}

/* Function name: write_callback
 * Description:   Called when data is ready to be written to the socket.
 * Arguments:     fd - Socket file descriptor.
 */
privatef void write_callback(int fd)
{
  string sockID;
  int    sock_return, times;

  if (member_array(fd, keys(Ids)) == -1) return;

  sockID = Ids[fd];

  if (member_array(sockID, keys(Sockets)) == -1 && !Sockets[sockID]) return;

  /* Not blocking at the moment */
  Sockets[sockID]["blocking"] = 0;

  /* If the socket is closed (close_callback called) then clean up */
  /* If we are closing and there is no data in the buffer then close the socket */
  if (Sockets[sockID]["closed"] || (!Sockets[sockID]["buffer"] && Sockets[sockID]["closing"]))
  {
    internal_close(fd);

    return;
  }

  /* Stop trying to write data for a bit */
  if (Sockets[sockID]["writeRetry"] == WRITERETRYLIMIT)
  {
    Sockets[sockID]["blocking"] = 1;
    Sockets[sockID]["writeRetry"] = 0;
    call_out("write_callback", 5, fd);

    return;
  }

  /* Pre-set the socket_write error to EESUCCESS */
  sock_return = EESUCCESS;

  times = 10;

  /* Process when:
   * there is data in the buffer
   * socket_write is successful
   * looped less than 10 times (limit spam)
   */
  while (Sockets[sockID]["buffer"] && sock_return == EESUCCESS && times > 0)
  {
    times--;
#if 0
    /* Turn this on for lots of log messages. */
    log(
      SOCKET_ID,
      "Notice",
      sprintf("Writing on %s/%d: %O",
      sockID,
      Sockets[sockID]["fd"],
      Sockets[sockID]["buffer"][0]
      )
    );
#endif

    if (   Sockets[sockID]["socketType"] == DATAGRAM
        || Sockets[sockID]["socketType"] == DATAGRAM_BINARY)
      sock_return = socket_write(fd, Sockets[sockID]["buffer"][0], Sockets[sockID]["hostport"]);
    else
      sock_return = socket_write(fd, Sockets[sockID]["buffer"][0]);

    /* Write first set of data in buffer to the socket */
    switch (sock_return)
    {
      /* Break out of the switch if successful */
      case EESUCCESS: break;

      /* Data has been buffered and we need to suspend further writes until it is cleared */
      case EECALLBACK:
      {
        Sockets[sockID]["blocking"] = 1;

        break;
      }

      /* The socket is blocked so we need to call write_callback again */
      case EEWOULDBLOCK:
      {
        call_out("write_callback", 1, fd);

        return;
      }

      /* Problem with send */
      case EESEND:
      {
        if (!Sockets[sockID]) return;
        if (member_array("writeRetry", keys(Sockets[sockID])) == -1)
          Sockets[sockID]["writeRetry"] = 0;

        Sockets[sockID]["writeRetry"] = Sockets[sockID]["writeRetry"] + 1;
        call_out("write_callback", 2, fd);

        return;
      }

      /* Flow control has been violated, shouldn't see this */
      case EEALREADY:
      {
        Sockets[sockID]["blocking"] = 1;

        return;
      }

      /* Something went really wrong so we close the socket */
      default:
      {
        error_log(sockID, Sockets[sockID]["type"] + "/socket_write", sock_return);
        internal_close(fd);

        return;
      }

    }

    Sockets[sockID]["writeRetry"] = 0;

    /* Remove the data we have written from the buffer */
    if (sizeof(Sockets[sockID]["buffer"]) == 1)
    {
      Sockets[sockID]["buffer"] = 0;

      /* If the socket is closed (close_callback called) then clean up */
      /* or if we are closing then close the socket */
      if (Sockets[sockID]["closed"] || Sockets[sockID]["closing"])
      {
        internal_close(fd);

        return;
      }
    }
    else Sockets[sockID]["buffer"] = Sockets[sockID]["buffer"][1..<1];
  }
}

/* Function name: internal_write
 * Description:   Internal socket write.
 * Arguments:     fd - Socket file descriptor.
 *                data - Outgoing data.
 */
privatef void internal_write(int fd, mixed data)
{
  string sockID;

  sockID = Ids[fd];

  /* Add data to the buffer */
  if (Sockets[sockID]["buffer"]) Sockets[sockID]["buffer"] += ({ data });
  else Sockets[sockID]["buffer"] = ({ data });

  /* If we are blocking then return otherwise write it */
  if (Sockets[sockID]["blocking"]) return;
  else
  {
    Sockets[sockID]["writeRetry"] = 0;
    write_callback(fd);
  }
}

/* Function name: log
 * Description:   Internal socket log.
 * Arguments:     sockID - Socket ID.
 *                pre - Pre-log message.
 *                str - Log message.
 */
privatef void log(string sockID, string pre, string str)
{
  /* If we have set a log file then append the message to it */
  if (member_array(sockID, keys(Sockets)) != -1)
    log_file(
      Sockets[sockID]["logfile"],
      sprintf("%s [SOCKET_D%s]: %s\n",
      pre,
      (sockID == SOCKET_ID ? "" : "/" + sockID),
      str
      )
    );
}

/* Function name: error_log
 * Description:   Internal socket error log.
 * Arguments:     sockID - Socket ID.
 *                str - Log message.
 *                x - Socket error code.
 */
privatef void error_log(string sockID, string str, int x)
{
  log(sockID, "Error", sprintf("%s - %s", str, socket_error(x)));
}

/* Function name: client_create
 * Description:   Create a socket for clients and connects it to host IP and port.
 * Arguments:     sockID - Unique ID that can be used to reference the socket.
 *                host - Host IP to connect to.
 *                port - Host port number.
 *
 *                Optional arguments:-
 *                extra - mapping of extra arguments:-
 *                  type - Socket Type.
 *                  persistent - Keeps the connection open at all times.
 *                  readf - User defined read callback function.
 *                  closef - User defined close callback function.
 *                  releasef - User defined release callback function.
 *                  logfile - Per-socket log file.
 *
 * Return:        Socket creation success or error code.
 */
varargs int client_create(string sockID, string host, int port, mapping extra)
{
  mapping sock;
  string  readf = 0, closef = 0, releasef = 0, logfile = 0;
  int     sock_return, persistent = 0, type = -1;

  if (undefinedp(sockID) || !stringp(sockID)) return EESOCKET;
  if (undefinedp(host) || !stringp(host)) return EESOCKET;
  if (undefinedp(port) || !intp(port)) return EESOCKET;
  if (member_array(sockID, keys(Sockets)) != -1) return EEISCONN;

  if (!undefinedp(extra))
  {
    if (!undefinedp(extra["type"])) type = extra["type"];
    if (!undefinedp(extra["persistent"])) persistent = extra["persistent"];
    if (!undefinedp(extra["readf"])) readf = extra["readf"];
    if (!undefinedp(extra["closef"])) closef = extra["closef"];
    if (!undefinedp(extra["releasef"])) releasef = extra["releasef"];
    if (!undefinedp(extra["logfile"])) logfile = extra["logfile"];
  }

  /* If we haven't set a socket type then set it to STREAM */
  if (type == -1) type = STREAM;

  /* Initialise client socket data */
  sock = ([
    "fd": -1,
    "owner": previous_object(),
    "type": "Client",
    "blocking": 0,
    "writeRetry": 0,
    "closing": 0,
    "closed": 0,
    "time": time(),
    "socketType": type,
    "persistent": persistent,
    "readf": readf,
    "closef": closef,
    "releasef": releasef,
    "logfile": logfile,
  ]);

  Sockets[sockID] = copy(sock);

  /* Create a socket */
  if (type == DATAGRAM || type == DATAGRAM_BINARY)
    sock_return = socket_create(type, "udp_client_read_callback");
  else
    sock_return = socket_create(type, "client_read_callback", "close_callback");

  /* Couldn't create a socket */
  if (sock_return < 0)
  {
    error_log(sockID, "Client/socket_create", sock_return);
    map_delete(Sockets, sockID);

    return sock_return;
  }

  /* Socket created and return value is a file descriptor we use */
  sock["fd"] = sock_return;
  Ids[sock["fd"]] = sockID;
  log(sockID, "Success", "Created client socket (" + sock["fd"] + ")");

  if (type != DATAGRAM && type != DATAGRAM_BINARY)
  {
    /* Bind the socket to a system selected port */
    /* Binding isn't necessary for clients since the system *should* do it for you */
    sock_return = socket_bind(sock["fd"], 0);

    /* Couldn't bind it */
    if (sock_return < 0)
    {
      error_log(sockID, "Client/socket_bind", sock_return);
      internal_close(sock["fd"]);

      return sock_return;
    }

    log(sockID, "Success", "Client socket bound to a port.");

    /* Now we actually connect the socket to a host and port */
    sock_return = socket_connect(sock["fd"], host + " " + port, "client_read_callback", "write_callback");

    /* Couldn't connect to remote machine */
    if (sock_return < 0)
    {
      error_log(sockID, "Client/socket_connect", sock_return);
      internal_close(sock["fd"]);

      return sock_return;
    }

    log(sockID, "Success", "Connected client to " + host + " " + port);
  }

  Sockets[sockID] = copy(sock);
  log(
    SOCKET_ID,
    "Notice",
    sprintf("Client created - %s/%d: %O", sockID, sock["fd"], Sockets[sockID])
  );

  /* Call the user function if one is set up otherwise do nothing */
  if (stringp(sock["releasef"]))
  {
    sock_return = socket_release(sock["fd"], sock["owner"], sock["releasef"]);

    if (sock_return != EESUCCESS)
    {
      error_log(sockID, "Client/socket_release", sock_return);

      return sock_return;
    }
  }

  /* Return the file descriptor on success */
  return sock["fd"];
}

/* Function name: server_create
 * Description:   Create a socket for a server and bind it to a port.
 * Arguments:     sockID - Unique ID that can be used to reference the socket.
 *                port - Server port number.
 *
 *                Optional arguments:-
 *                extra - mapping of extra arguments:-
 *                  type - Socket Type.
 *                  readf - User defined read callback function.
 *                  closef - User defined close callback function.
 *                  listenf - User defined listen callback function.
 *                  releasef - User defined release callback function.
 *                  logfile - Per-socket log file.
 *
 * Return:        Socket creation success or error code.
 */
varargs int server_create(string sockID, int port, mapping extra)
{
  mapping sock;
  string  readf = 0, closef = 0, listenf = 0, releasef = 0, logfile = 0;
  int     sock_return, type = -1;

  if (undefinedp(sockID) || !stringp(sockID)) return EESOCKET;
  if (undefinedp(port) || !intp(port)) return EESOCKET;
  if (member_array(sockID, keys(Sockets)) != -1) return EEISCONN;

  if (!undefinedp(extra))
  {
    if (!undefinedp(extra["type"])) type = extra["type"];
    if (!undefinedp(extra["readf"])) readf = extra["readf"];
    if (!undefinedp(extra["closef"])) closef = extra["closef"];
    if (!undefinedp(extra["listenf"])) listenf = extra["listenf"];
    if (!undefinedp(extra["releasef"])) releasef = extra["releasef"];
    if (!undefinedp(extra["logfile"])) logfile = extra["logfile"];
  }

  /* If we haven't set a socket type then set it to STREAM */
  if (type == -1) type = STREAM;

  /* Initialise server socket data */
  sock = ([
    "fd": -1,
    "owner": previous_object(),
    "type": "Server",
    "blocking": 0,
    "writeRetry": 0,
    "buffer": 0,
    "closing": 0,
    "closed": 0,
    "time": time(),
    "persistent": 1,
    "socketType": type,
    "readf": readf,
    "closef": closef,
    "listenf": listenf,
    "releasef": releasef,
    "logfile": logfile,
  ]);

  Sockets[sockID] = copy(sock);

  /* Create a socket */
  if (type == DATAGRAM || type == DATAGRAM_BINARY)
    sock_return = socket_create(type, "udp_server_read_callback");
  else
    sock_return = socket_create(type, "server_read_callback", "close_callback");

  /* Couldn't create a socket */
  if (sock_return < 0)
  {
    error_log(sockID, "Server/socket_create", sock_return);
    map_delete(Sockets, sockID);

    return sock_return;
  }

  /* Socket created and return value is a file descriptor we use */
  sock["fd"] = sock_return;
  Ids[sock["fd"]] = sockID;
  log(sockID, "Success", "Created server socket (" + sock["fd"] + ")");

  /* Bind the socket to the supplied port */
  sock_return = socket_bind(sock["fd"], port);

  /* Couldn't bind it */
  if (sock_return < 0)
  {
    error_log(sockID, "Server/socket_bind", sock_return);
    internal_close(sock["fd"]);

    return sock_return;
  }

  log(sockID, "Success", "Server socket bound to port " + port);

  if (type != DATAGRAM && type != DATAGRAM_BINARY)
  {
    /* Now we set up the listen callback */
    sock_return = socket_listen(sock["fd"], "listen_callback");

    /* Couldn't set up the socket to listen */
    if (sock_return < 0)
    {
      error_log(sockID, "Server/socket_listen", sock_return);
      internal_close(sock["fd"]);

      return sock_return;
    }

    log(sockID, "Success", "Server listen callback set up.");
  }

  Sockets[sockID] = copy(sock);
  log(
    SOCKET_ID,
    "Notice",
    sprintf("Server created - %s/%d: %O", sockID, sock["fd"], Sockets[sockID])
  );

  /* Call the user function if one is set up otherwise do nothing */
  if (stringp(sock["releasef"]))
  {
    sock_return = socket_release(sock["fd"], sock["owner"], sock["releasef"]);

    if (sock_return != EESUCCESS)
    {
      error_log(sockID, "Server/socket_release", sock_return);

      return sock_return;
    }
  }

  /* Return the file descriptor on success */
  return sock["fd"];
}

/* Function name: close
 * Description:   Public socket close.
 * Arguments:     fd - Socket file descriptor or ID.
 *
 *                Optional argument:-
 *                delay - Delays the closing of the socket for 'delay' seconds.
 */
varargs void close(mixed fd, int delay)
{
  if (undefinedp(delay)) delay = 0;

  if (intp(fd))
  {
    if (member_array(fd, keys(Ids)) == -1) return;
    if (Sockets[Ids[fd]]["owner"] != previous_object())
    {
      error_log(Ids[fd], "close/sockID '" + Ids[fd] + "' in object '" + Sockets[Ids[fd]]["owner"], EESECURITY);

      return;
    }

    call_out("internal_close", delay, fd);
  }
  else
  if (stringp(fd))
  {
    if (member_array(fd, keys(Sockets)) == -1) return;
    if (Sockets[fd]["owner"] != previous_object())
    {
      error_log(fd, "close/sockID '" + Ids[fd] + "' in object '" + Sockets[fd]["owner"], EESECURITY);

      return;
    }

    call_out("internal_close", delay, Sockets[fd]["fd"]);
  }
}

/* Function name: client_write
 * Description:   Public client socket write.
 * Arguments:     fd - Socket file descriptor.
 *                data - Outgoing data.
 */
void client_write(mixed fd, mixed data)
{
  if (intp(fd))
  {
    if (member_array(fd, keys(Ids)) == -1) return;
    if (Sockets[Ids[fd]]["owner"] != previous_object())
    {
      error_log(Ids[fd], "client_write/sockID '" + Ids[fd] + "' in object '" + Sockets[Ids[fd]]["owner"], EESECURITY);

      return;
    }

    if (   Sockets[Ids[fd]]["socketType"] == DATAGRAM
        || Sockets[Ids[fd]]["socketType"] == DATAGRAM_BINARY)
    {
      error_log(Ids[fd], "client_write/sockID '" + Ids[fd] + "' in object '" + Sockets[Ids[fd]]["owner"], EETYPENOTSUPP);

      return;
    }

    internal_write(fd, data);
  }
  else
  if (stringp(fd))
  {
    if (member_array(fd, keys(Sockets)) == -1) return;
    if (Sockets[fd]["owner"] != previous_object())
    {
      error_log(fd, "client_write/sockID '" + Ids[fd] + "' in object '" + Sockets[fd]["owner"], EESECURITY);

      return;
    }

    if (   Sockets[fd]["socketType"] == DATAGRAM
        || Sockets[fd]["socketType"] == DATAGRAM_BINARY)
    {
      error_log(fd, "client_write/sockID '" + fd + "' in object '" + Sockets[fd]["owner"], EETYPENOTSUPP);

      return;
    }

    internal_write(Sockets[fd]["fd"], data);
  }
}

/* Function name: server_write
 * Description:   Public server socket write.
 * Arguments:     fd - Socket file descriptor.
 *                data - Outgoing data.
 *                close - If set to '0' the connection is kept open after writing the data.
 *                If set to '1' the connection is closed after writing the data.
 */
void server_write(mixed fd, mixed data, int close)
{
  if (undefinedp(close)) close = 0;

  if (intp(fd))
  {
    if (member_array(fd, keys(Ids)) == -1) return;
    if (Sockets[Ids[fd]]["owner"] != previous_object())
    {
      error_log(Ids[fd], "server_write/sockID '" + Ids[fd] + "' in object '" + Sockets[Ids[fd]]["owner"], EESECURITY);

      return;
    }

    if (   Sockets[Ids[fd]]["socketType"] == DATAGRAM
        || Sockets[Ids[fd]]["socketType"] == DATAGRAM_BINARY)
    {
      error_log(Ids[fd], "server_write/sockID '" + Ids[fd] + "' in object '" + Sockets[Ids[fd]]["owner"], EETYPENOTSUPP);

      return;
    }

    Sockets[Ids[fd]]["closing"] = close;
    internal_write(fd, data);
  }
  else
  if (stringp(fd))
  {
    if (member_array(fd, keys(Sockets)) == -1) return;
    if (Sockets[fd]["owner"] != previous_object())
    {
      error_log(fd, "server_write/sockID '" + Ids[fd] + "' in object '" + Sockets[fd]["owner"], EESECURITY);

      return;
    }

    if (   Sockets[fd]["socketType"] == DATAGRAM
        || Sockets[fd]["socketType"] == DATAGRAM_BINARY)
    {
      error_log(fd, "server_write/sockID '" + fd + "' in object '" + Sockets[fd]["owner"], EETYPENOTSUPP);

      return;
    }

    Sockets[fd]["closing"] = close;
    internal_write(Sockets[fd]["fd"], data);
  }
}

/* Function name: udp_write
 * Description:   Public UDP socket write.
 * Arguments:     fd - Socket file descriptor.
 *                data - Outgoing data.
 *                addr - Address ("IP PORT") of the outgoing data.
 */
void udp_write(mixed fd, mixed data, string addr)
{
  if (intp(fd))
  {
    if (member_array(fd, keys(Ids)) == -1) return;
    if (Sockets[Ids[fd]]["owner"] != previous_object())
    {
      error_log(Ids[fd], "udp_write/sockID '" + Ids[fd] + "' in object '" + Sockets[Ids[fd]]["owner"], EESECURITY);

      return;
    }

    if (   Sockets[Ids[fd]]["socketType"] != DATAGRAM
        && Sockets[Ids[fd]]["socketType"] != DATAGRAM_BINARY)
    {
      error_log(Ids[fd], "udp_write/sockID '" + Ids[fd] + "' in object '" + Sockets[Ids[fd]]["owner"], EETYPENOTSUPP);

      return;
    }

    Sockets[Ids[fd]]["hostport"] = addr;
    internal_write(fd, data);
  }
  else
  if (stringp(fd))
  {
    if (member_array(fd, keys(Sockets)) == -1) return;
    if (Sockets[fd]["owner"] != previous_object())
    {
      error_log(fd, "udp_write/sockID '" + Ids[fd] + "' in object '" + Sockets[fd]["owner"], EESECURITY);

      return;
    }

    if (   Sockets[fd]["socketType"] != DATAGRAM
        && Sockets[fd]["socketType"] != DATAGRAM_BINARY)
    {
      error_log(fd, "udp_write/sockID '" + fd + "' in object '" + Sockets[fd]["owner"], EETYPENOTSUPP);

      return;
    }

    Sockets[fd]["hostport"] = addr;
    internal_write(Sockets[fd]["fd"], data);
  }
}

/* Function name: query_socket_info
 * Description:   Queries the internal Sockets mapping.
 * Return:        All connected sockets.
 */
mapping query_socket_info()
{
  mapping tmp = ([ ]);

  foreach (string sockID, mapping sock in Sockets)
  {
    if (sockID == SOCKET_ID) continue;

    tmp[sockID] = sock;
  }

  return copy(tmp);
}


Evennia also already runs IRC/MUD bridge:
http://code.google.com/p/evennia/wiki/IRC

Now we just need someone to port them to current version of Dead Soul.