Tiny MUD Server - version 2

Posted by Nick Gammon on Tue 27 Jul 2004 12:31 AM — 112 posts, 471,868 views.

Australia Forum Administrator #0

I am pleased to release an improved version of the "tiny MUD server" which I had previously released a year ago. This has compiled without errors or warnings using g++ on:

  • Linux
  • Cygwin (Windows)
  • Macintosh OS/X

This program is only a simple example (under 1500 lines of code in a single source file), however if you are interested in seeing how a server works, or wanting to write your own, you could use this as a basis for it.

The reason I wrote it is that sometimes I want to see how to make a simple server, but the main 'established' servers tend to be thousands of lines of code, and it is hard to see in the middle of all that code what is essential to writing a server, and what is an add-in.

The code is Copyright 2004 by myself, however you are permitted to copy, use, modify, sell and distribute it provided this copyright notice appears in all copies. This gets around the problem that people have by deriving a server from Diku or other MUDs which have restrictive licenses on them (eg. no pay MUDs). The Copyright notice is simply to stop people taking the code and adding their own, more restrictive, copyright to it.

Download it from http://www.gammon.com.au/files/muds/tinymudserver_v2.tgz (16 Kb) or http://www.gammon.com.au/files/muds/tinymudserver_v2.zip (18 Kb).

Description

This program demonstrates a simple MUD (Multi-User Dungeon) server - in a single file.

It does the following:

  • Accepts multiple connections from players
  • Maintains a list of connected players
  • Asks players for a name and password
  • Saves player files to disk (name, password, current room, player flags)
  • Implements the commands: quit, look, say, tell, help, goto, transfer, shutdown, setflag, clearflag
  • Implements movement commands (eg. n, s, e, w)
  • Illustrates sending messages to a single player (eg. a tell) or all players (eg. a say)
  • Handles players disconnecting or quitting
  • Illustrates a "connection dialog" - players get asked their name, then their password.
  • Allows new players to create a character by specifying a name and password.
  • Demonstrates using the Standard Template Library for lists, strings, vectors, maps and sets.
  • Illustrates periodic messages using a timer (at present it just shows a message every few seconds)
  • Illustrates rudimentary player control (eg. gagging players from talking)
  • Loads room descriptions and exits from a disk file
  • Loads messages from a disk file
  • Loads control parameters from a disk file

What you could add

As it stands the program is too simple to be used for a full MUD, however it could be the basis for writing your own. You would want to add things like this:

  • Objects (eg. swords), taking/dropping things, etc.
  • Fighting (if required)
  • Building/extending online
  • Logging events (eg. connections, disconnections, faults)
  • Colour
  • MCCP (compression)
  • MXP (MUD Extension Protocol)
  • Telnet negotiation

Easy to modify

The code has been designed to allow for easy additions, as a lot of "helper" routines have already been written. In the following posts I will describe how to add extra commands, to show the general idea.


Example session

Initial connection


Welcome to the Tiny MUD Server version 2.0.0

|-------------------- Tiny MUD Server --------------------|
|
| Written by Nick Gammon.
|
| July 2004.
|
| http://www.gammon.com.au/
|
| Welcome to tinymudserver version 2! Have fun. :)
|---------------------------------------------------------|

Enter your name, or 'new' to create a new character ...  

Creating a new player


Enter your name, or 'new' to create a new character ...  
new
Please choose a name for your new character ... 
Magnum
Choose a password for Magnum ... 
swordfish
Re-enter password to confirm it ... 
swordfish
Welcome, Magnum



Welcome to our MUD! 
Please read the help files to become familiar with our rules. :)


Message Of The Day (MOTD)

Here is where you place announcements to be given to people 
once they have joined the game.

Starting room (room 1000).
You are standing in a fabulous, fabulous, exotic room, with 
all sorts of things that go 'hum' and 'plink' in the night.

As you glance around the room, you wonder what else might exist in this world.
Exits: e n s w 
> You hear creepy noises ...

Connecting to an existing player


Enter your name, or 'new' to create a new character ...  
magnum
Enter your password ... 
swordfish
Welcome, Magnum



Welcome back! We hope you enjoy playing today.


Message Of The Day (MOTD)

Here is where you place announcements to be given to people 
once they have joined the game.

Starting room (room 1000).
You are standing in a fabulous, fabulous, exotic room, with 
all sorts of things that go 'hum' and 'plink' in the night.

As you glance around the room, you wonder what else might 
exist in this world.
Exits: e n s w 
> You hear creepy noises ...

Moving and talking


n
You go n
Room 1004.
This is the training room. One day you might receive all 
sorts of interesting training here. 
Perhaps you might even purchase shields and armour. But not today.
Exits: s 
You also see Nick.
> 
say hi there
You say, "hi there"
> 

The room descriptions and exits have been read from a rooms.txt file. Most of the messages are in a messages.txt file (eg. the message-of-the-day).

Amended on Thu 13 Oct 2005 01:22 AM by Nick Gammon
Australia Forum Administrator #1
Adding to the MUD

Adding commands is easy. Basically you add a "command handler", and add the command to a "command map". Here is an example, of adding the "save" command.

First, write the command handler. This is easy in this case because the save routine is already written (it automatically saves players when they disconnect).


void DoSave  (tPlayer * p, istream & sArgs)
{
  p->Save ();
  *p << "Saved.\n";  
}


We place this routine somewhere logical, like after DoSay or in a similar place.

Each command handler is passed the current player (the one who typed the command), and the arguments that player typed, as an input stream. In this case we don't use the arguments.

Using the operator << sends a message to that player.

Finally we add the save command to the command map in the LoadThings function, so it recognises the word "save", like this:


 commandmap ["setflag"]  = DoSetFlag;
 commandmap ["clearflag"]= DoClearFlag;
 commandmap ["save"]     = DoSave; // <-- add this here





Another example, chatting

To chat, we need to process the command argument (to find what to chat), and send that to all players. We'll also illustrate using a player flag. You can set any number of flags on players, you either need a flag set (eg. can_shutdown) to permit an action, if you want, or not set (eg. gagged) to inhibit an action.

First we will test the "gagged" flag, so that gagged players cannot chat. This will throw an exception if the player cannot chat.

Next, we use the input stream "sArgs" to find *what* to chat. This also throws an exception if nothing is there, the message "Chat what?" is then sent to the player.

If we pass both tests we use SendToAll to send the chat message to all connected players.


void DoChat (tPlayer * p, istream & sArgs)
{
  p->NeedNoFlag ("gagged"); // can't if gagged
  string what = GetMessage (sArgs, "Chat what?");  // what  
  SendToAll (p->playername + " chats, \"" + what + "\"\n");  // chat it
}


Then we add the new command to the command map also:


  commandmap ["save"]     = DoSave;      // save a player
  commandmap ["chat"]     = DoChat;      // chat
Amended on Tue 27 Jul 2004 06:19 AM by Nick Gammon
Australia Forum Administrator #2
Another new command - emote

The emote command is used to show emotions or actions, eg.


emote sits down
Nick sits down.


This is shown to everyone in the current room.

To do this we will use a variation of the SendToAll routine, which lets you specify which room to send to. By specifying the current player's room, only the people in that room will see it:


void DoEmote (tPlayer * p, istream & sArgs)
{
  string what = GetMessage (sArgs, "Emote what?");  // what  
  SendToAll (p->playername + " " + what + "\n", 0, p->room);  // emote it
}


Once again we add the new command to the command map:


 commandmap ["save"]     = DoSave;      // save a player
 commandmap ["chat"]     = DoChat;      // chat
 commandmap ["emote"]    = DoEmote;     // emote
Amended on Tue 27 Jul 2004 01:29 AM by Nick Gammon
Australia Forum Administrator #3
A more complex command - who

Now I'll illustrate a more complex command - the "who" list. This is more complicated because we need to iterate over the connected player list, showing each one to the one who requested it. There are a number of ways you could to it, but I'll choose to use a functor combined with the for_each STL algorithm.


// functor for doing who list
struct whoList
{
  tPlayer * sendto;
  int count;
 
  // ctor
  whoList (tPlayer * p) : sendto (p), count (0) {}
  // who list item
  void operator() (tPlayer * p) 
    {
    if (p->IsPlaying ())
      {
      *sendto << "  " << p->playername << " in room " << p->room << "\n";
      ++count;
      }
    } // end of operator() 
  int GetCount () const { return count; }
  
};  // end of whoList

void DoWho (tPlayer * p, istream & sArgs)
{
  NoMore (p, sArgs);  // check no more input
  *p << "Connected players ...\n";
  *p << for_each (playerlist.begin (), playerlist.end (), 
                  whoList (p)).GetCount () 
     << " player(s)\n";  
} // end of DoWho


This also uses NoMore to verify that the player didn't type something after "who" (like "who nick").

The for_each algorithm walks the entire player list, passing each item in it to the whoList functor. The constructor for whoList remembers which player wants to see the who list, and then the operator() is repeatedly called for each player in the list. We use IsPlaying to check if the player in the list is playing yet (and not just entering their name and password, for example), and if so, we display their name and current room.

We also add one to a counter for each connected player. Finally the for_each algorithm returns a reference to the whoList function, which we use to call GetCount, to get at this counter, and display the count to the player, like this:


who
Connected players ...
  Nick in room 1000
  Magnum in room 1000
2 player(s)


The only remaining thing to do is to add the DoWho command to the command map ...


  commandmap ["emote"]    = DoEmote;     // emote
  commandmap ["who"]      = DoWho;       // who is on?




An alternative way of doing "who"

Below is an alternative implementation for DoWho. It is all in a single function, and might look a bit more "natural" if you are used to C-style loops ...


void DoWho (tPlayer * p, istream & sArgs)
{
  NoMore (p, sArgs);  // check no more input
  *p << "Connected players ...\n";
  
  int count = 0;
  for (tPlayerListIterator iter = playerlist.begin ();
       iter != playerlist.end ();
       ++iter)
    {
    tPlayer * pTarget = *iter;    // the player  
    if (pTarget->IsPlaying ())
      {
      *p << "  " << pTarget->playername << 
            " in room " << pTarget->room << "\n";
      ++count;
      } // end of if playing
    } // end of doing each player
  
  *p << count << " player(s)\n";  
} // end of DoWho
Amended on Tue 27 Jul 2004 03:25 AM by Nick Gammon
Australia Forum Administrator #4

I have now done a slightly different version of the above server, this time broken up from a single source file into various .cpp and .h files, to make it easier to modify and expand. This has the functionality of the earlier version, plus the extra commands described above (save, chat, emote, who).

The purpose of the various files are:

.cpp files

  • commands.cpp - commands handler (eg. DoQuit, DoLook etc.)
  • comms.cpp - communications stuff - handle new connections, read and write to connected players
  • globals.cpp - global data (player list, room list etc.)
  • load.cpp - load things (rooms, messages, control file)
  • player.cpp - player-related functions (load, save, read, write)
  • room.cpp - room-related functions
  • states.cpp - connection states (get player name, password etc.)
  • strings.cpp - string utilities (find-and-replace, trim spaces etc.)
  • tinymudserver.cpp - main program

.h files

  • constants.h - various constants (prompt, version, port etc.)
  • globals.h - external declarations for global data (player list, etc.)
  • player.h - player class
  • room.h - room class
  • strings.h - string utilites declarations
  • utils.h - assorted other utilities

There is also an enhanced Makefile which correctly compiles the various .cpp files if you type "make". It also has dependency code in it, so if you change a .h file, the appropriate .cpp files will be automatically recompiled.

Download it from http://www.gammon.com.au/files/muds/tinymudserver_v2_2.tgz (18 Kb).

The total number of lines has increased to 1863, partly because of some duplication (multiple #include directives for the same file, in different source files), and partly because of the extra commands which have been added. There is also an author and copyright notice at the start of each file, which adds about 13 lines per file. The breakdown for each file is:


    354    1632   11336 commands.cpp
    335    1235    9403 comms.cpp
     41     170    1229 globals.cpp
    154     566    4128 load.cpp
    236     988    6544 player.cpp
     33     109     816 room.cpp
    203     790    6206 states.cpp
    118     493    3278 strings.cpp
     67     230    1603 tinymudserver.cpp
     28     192    1463 constants.h
     28     125     978 globals.h
    133     641    4528 player.h
     28      87     615 room.h
     71     316    2288 strings.h
     34     146     929 utils.h
   1863    7720   55344 total
Amended on Thu 13 Oct 2005 01:24 AM by Nick Gammon
Australia Forum Administrator #5
Adding more questions for new players

The next step will be an illustration of how to add more questions for newly-created players. For instance, what class they want (eg. mage, warrior etc.).



Step 1 - add a list of classes

In globals.cpp, add this:


// classes
set<std::string, ciLess> classset;


In globals.h, add this:


// classes
extern std::set<std::string, ciLess> classset;




Step 2 - read the available classes from the control file

In load.cpp, add this:


  LoadSet (fControl, blockedIP);    // blocked IP addresses
  LoadSet (fControl, classset);     // class names




Step 3 - add the new connection state

In player.h, add a new connection state:


  eAwaitingNewName,     // they have typed 'new'    
  eAwaitingNewPassword, // we want a new password
  eConfirmPassword,     // confirm the new password
  eAwaitingClass,       // we want the class name




Step 4 - add the player "class" to the player class

In player.h, add the new variable to the player:


  bool closing;     // true if they are about to leave us
  std::set<string, ciLess> flags;  // player flags
  string playerclass;   // what class the player is


Note that we cannot call it "class" because that is a reserved word in C++.



Step 5 - process the new connection state

In states.cpp, we need a new include file:


#include <stdexcept>
#include <fstream>
#include <iostream>
#include <iterator>


In states.cpp, in the function LoadState, we need to add the new state to the state map:


  statemap [eAwaitingNewName]     = ProcessNewPlayerName; // new player
  statemap [eAwaitingNewPassword] = ProcessNewPassword;
  statemap [eConfirmPassword]     = ProcessConfirmPassword;
  statemap [eAwaitingClass]       = ProcessClass;


In states.cpp, once they have confirmed their password, they are not playing yet, so in function ProcessConfirmPassword change:


 // New player now in the game
 PlayerEnteredGame (p, messagemap ["new_player"]);


to:


  // now get their class
  p->connstate = eAwaitingClass;
  ostringstream os;
  copy (classset.begin (), classset.end (), 
        ostream_iterator<string> (os, " "));
  p->prompt = MAKE_STRING ("Classes are: " << os.str () 
              << "\nPlease choose a class ... ");


This sets the player "state" to "awaiting class", and then uses a "copy" to create a list of available classes. The prompt is changed to display that, and the player is asked for their class.

Finally, we add the handler for the eAwaitingClass state:


void ProcessClass (tPlayer * p, istream & sArgs)
{
   string playerclass;
   sArgs >> playerclass;
  
  if (playerclass.empty ())
    return;   // can't be empty, this will make it reprompt for it
  
  if (classset.find (playerclass) == classset.end ())
    throw runtime_error ("That class is not in the list of classes.");
  
  p->playerclass = playerclass;
  
  // New player now in the game
  PlayerEnteredGame (p, messagemap ["new_player"]);
         
} /* end of ProcessClass */


This checks that the entered class is not empty, and is in the valid list. If so, it stores it in the player's playerclass field, and lets the player enter the game.



Last, but not least, we enter the list of classes in the control.txt file:


n s e w u d ne nw se sw enter leave
new god admin quit n s e w u d look me self
10.1.2.3
Mage Cleric Thief Warrior Vampire Druid Ranger


The four lines above are:

  1. Valid directions (n, s, e, w etc.)
  2. Disallowed new player names
  3. Banned IP addresses
  4. List of class names





This might look complicated, but it is only a few lines of code, and could be extended along similar lines to ask other questions. Now, to see it in action ...


Enter your name, or 'new' to create a new character ...  
new
Please choose a name for your new character ... 
Ceritram
Choose a password for Ceritram ... 
swordfish
Re-enter password to confirm it ... 
swordfish
Classes are: Cleric Druid Mage Ranger Thief Vampire Warrior 
Please choose a class ... 
blah
That class is not in the list of classes.
Classes are: Cleric Druid Mage Ranger Thief Vampire Warrior 
Please choose a class ... 
cleric
Welcome, Ceritram

Welcome to our MUD! 

... and so on ...

Amended on Tue 27 Jul 2004 07:16 AM by Nick Gammon
Australia Forum Administrator #6
Amended posts above because they contained ftp download URLs which are no longer supported, and replaced them by http ones instead.
#7
Can you add an invisible flag ?
Australia Forum Administrator #8
You can do anything you want. This is an example of coding a MUD server. You need to understand how the code works and make such changes that you want.
#9
Hello ! I succeeded add some little command and colors, but how can I add an other %r like in the following code:


while (!(fMessages.eof ()))
{
string sMessageCode, sMessageText;
fMessages >> sMessageCode >> ws;
getline (fMessages, sMessageText);
if (!(sMessageCode.empty ())){
messagemap [tolower (sMessageCode)] =
FindAndReplace (sMessageText, "%r", "\n");
}


For exemple %yellow% replaced by "\33[1;32m"

Thank
Amended on Sun 15 Jan 2006 01:01 AM by Conap
Australia Forum Administrator #10
Sure, you could do something like:


messagemap [tolower (sMessageCode)] =
  FindAndReplace (messagemap [tolower (sMessageCode)],
       "%yellow%", 
       "\33[1;32m");


However if you are planning to replace %red% by the code for red, remember that %r is already a newline.
#11
Thank !
#12
I have added all that commands:

// commands
commandmap ["look"] = DoLook; // look around
commandmap ["l"] = DoLook; // synonymm for look
commandmap ["quit"] = DoQuit; // bye bye
commandmap ["say"] = DoSay; // say something
commandmap ["\""] = DoSay; // synonym for say
commandmap ["tell"] = DoTell; // tell someone
commandmap ["shutdown"] = DoShutdown; // shut MUD down
commandmap ["help"] = DoHelp; // show help message
commandmap ["goto"] = DoGoTo; // go to room
commandmap ["transfer"] = DoTransfer; // transfer someone else
commandmap ["setflag"] = DoSetFlag; // set a player's flag
commandmap ["clearflag"]= DoClearFlag; // clear a player's flag
commandmap ["shout"] = DoShout; // shout
commandmap ["emote"] = DoEmote; // emote
commandmap ["who"] = DoWho; // who
commandmap ["gecho"] = DoGecho; // gecho
commandmap ["echo"] = DoEcho; // echo
commandmap ["invisible"] = DoInvisible; //invisible
commandmap ["invis"] = DoInvisible; //invisible
commandmap ["board"] = DoBoard; //board
commandmap ["recall"] = DoRecall; //recall
commandmap ["reboot"] = DoReboot; //reboot
commandmap ["order"] = DoOrder; //order
commandmap ["gecho_blue"] = DoBlueGecho; //blue gecho
commandmap ["gecho_red"] = DoRedGecho; //red gecho
commandmap ["gecho_green"] = DoGreenGecho; //green gecho
commandmap ["gecho_yellow"] = DoYellowGecho; //yellow gecho
commandmap ["at"] = DoAt; // commande at
commandmap ["wizhelp"] = DoWizhelp; //commande wizhelp
commandmap ["title"] = DoTitle; // commande title
commandmap ["openportal"] = DoOpenPortal;
commandmap ["portal"] = DoPortal;
commandmap ["where"] = DoWhere;
commandmap ["teleport"] = DoTeleport;
commandmap ["news"] = DoNews;
commandmap ["kick"] = DoKick;
commandmap ["roomflag"] = DoRoomFlag;

(And colors)

But for the fight system ... the bots and the items ... I have NO idea

Please, help me :'(
Amended on Thu 09 Feb 2006 11:47 PM by Conap
USA #13
Out of curiosity, is there a reason in particular why you're trying to build from nearly scratch, instead of using an existing code-base? Writing from scratch or from nearly scratch is very difficult, and should be undertaken only by fairly advanced programmers. After all, implementing the fight system, mobs and objects is about 75% of the work that goes into a MUD.
#14
Yes, I know ...
USA #15
What kind of help are you looking for, then? SMAUG has lots of examples of how you'd go about doing this kind of thing. There's going to be tons of work to do, but there's a big example right there. What are your specific issues?
Australia Forum Administrator #16
Adding fighting and bots is no trivial task, but the basic idea is to use timers (which are already in the server) to periodically do things, like make the mobs move, or if they are fighting, do the next round of the fight.

You need to decide on what your fight rules are, what attributes affect fights (eg. health, mana, stamina, dexterity and so on), what spells you know, what attack method the player (and the mob) chooses, what happens when they attack (eg. does it miss) and so on.
USA #17
If you're building from scratch, then take advantage of that.

Don't try to emulate what Smaug offers, but allow yourself to try new things that no other codebase offers.

I'd like to see more interactive and interesting combat.

Either do something like Operation Overkill, where the player's reflexes come into play (keeping the player tense/alert as opposed to falling asleep at the keyboard) or allow for some strategy.

For instance, creating a Rock/Paper/Scissors type combat system might spice things up.

There could be three different offensive and three different defensive positions or manuevers. A trumps B which trumps C which trumps A...

Or you could do something like...

Aggressive (+X power / -X defense)
Precise (+X to-hit / -X speed)
Defensive (+X defense / -X power)
Quick (+X speed / -X to-hit)

I'd also love to see a good implementation of ranged/melee combat. Again, I'll refer to the old Operation Overkill BBS/Door game.

When combat starts, you check for which parties are aware of each other. Is the PC suprised? Is the PC surprising the mob? Make stealth and awareness skills worthwhile here to everyone, not just rouges/thieves.

Next, determine range at which combat starts. Psuedo-code:
PC or mob enters room:
Make opposing skill checks
PCAware = PCAwareSkillCheck - MobStealthSkillCheck
MobAware = MobAwareSkillCheck - PCStealthSkillCheck
if ((PCAware || MobAware) > X)
    range=1
    enter combat
else move to closer range
endif
PCAware = PCAwareSkillCheck - MobStealthSkillCheck
MobAware = MobAwareSkillCheck - PCStealthSkillCheck
if ((PCAware || MobAware) > X)
    range=0
    enter combat
else ships passing in the night
endif

if (PCAware > MobAware) MobSurprised
elseif (MobAware > PCAware) PCSurprised
else random init-check
endif


You then have the options to change range status, which takes a turn. You either try to create distance, or close distance. At range, only ranged weapons work. In close proximity, ranged weapons take a significant penalty.

If you are in melee range, and you try to create distance, the other party takes a free swipe at you.

Possibly, if they succeed at hitting you, you fail to create distance.

Not HORRIBLY complex to code, but adds plenty of spice to MUD combat, which is often a snooze-fest.

The pain is to handle checks of various parties leaving and entering rooms. The above system is a simplification for a single player experience. How many players/mobs do you keep track of being aware of in an X-room vicinity?

You could only include parties in one room, which seems to be the simplest. Leaving a room while someone is engaged with you allows said party to take another free swipe. Once both parties are in the same room again, do you go straight to an init roll?

As far as attributes, I think most systems for equity should have:

Damage Governing Attribute
To-Hit Governing Attribute
Defense Governing Attribute
Soak/Con/HP Governing Attribute
(these 4 balance well against each other)
Speed/Init Governing Attribute
(balances directly against itself with an opponent)
Int/Chi/Mana Governing Attribute

Anyone can then choose to expand past there for skill-specific attributes, or multiple magic systems, whatever.

A person could add social attributes, mental health, willpower, genetalia size, whatever. The point is that combat is well balanced, and yet the system is expandable beyond that.
Amended on Thu 16 Feb 2006 11:16 AM by Enderandrew
Australia Forum Administrator #18
Very interesting ideas. The ranged combat is a can of worms itself because of the problem of representing range in a text MUD, but it would be interesting to do things along the lines of what you described.
USA #19
It will be interesting to tackle all of these issues in the near future. Luckily I have some open minded designers to help with everything.

Oh, this isn't really the place, but any news on BabbleMUD? The site says a release was scheduled for mid-2005, which obviously has passed. Of course real life often prevents work on such projects!

If you ever get a release out be sure to post about it, because I for one would take it for a test spin and quite possibly use it over what I'm running currently.
USA #20
We started writing code about three times and stopped every time because we hadn't hammered out the design and found ourselves charging in a direction without knowing exactly what that direction was. Not only that, but we all got very busy with that pesky real life stuff.

Now, we've stopped writing code and are laying out as precisely as we can the game design, and as we decide on game design, the code design. In theory, coding it up should be "relatively easy" once the design is all set. But everybody knows what tends to happen to "in theory" plans... :-)
USA #21
I neglected to mention the idea for "reflexes-based" combat.

It shouldn't completely replace stats, but it makes combat more interesting. Again, this was stolen from Operation Overkill.

When you take a swing, the game will tell you to hit the space bar on 3. Then it starts with a random number and starts outputting text fairly fast...

......11111......22222.....33

And then you hit space bar!

The closer you get, the bigger of a bonus you get to your skill check, and vice-versa. You could just replace the random element of the skill-check with this, and keep the stat based natural bonuses.

Some players may not care for "twitch-gameplay", but there are tons of muds out there with standard and simple combat. I say spice it up and do something different. It should keep players awake at the keyboard.
USA #22
Except hitting the space bar won't do anything. You have to actually send it to the MUD, thus you'll have to hit enter (depending on client) to sent it. So it's really spacebar, then enter. Doesn't exactly work all that well.

Unless you design your own client to work on with MUD.
USA #23
You could always just use empty lines instead of the space, though.

Although, I have to admit I'm not sure I like the idea of having to keep hitting the button. I mean, Diablo gets kind of tiring when you have to clickclickclickclickclickclickclickclickclickclickclickclick non-stop. :)
USA #24
Actually, the purpose of such a thing would be to make it exactly UNLIKE Diablo.

As it is, you can just hit Attack non-stop in most Muds. There is little to nothing to combat. Adding a variety of options, or reflex based combat would force the player to pay more attention to combat. It would add strategy, tension and enjoyment to combat.

With Diablo you simply mindlessly click.

Adding more player involvement is a good thing.

I'm seriously thinking that working from a new, fairly clean codebase is a good way to go as opposed to working with a bloated, buggy one.

A new codebase has great potential. I am fairly new to C and C++ however, and would love to see more of the "meat" tackled before I started with such a codebase. Honestly, instead of the ten-million forked derivatives we have, I'd love to see a new codebase emerge that applies all the various lessons we've learned over the years.

A new codebase should be fast, secure, streamlined, portable, etc.

C++ and Lua or MySQL really do seem to be the way to go. So this project has great potential.

In an ideal universe, I'd love to see a codebase support "modules". The core codebase can be updated then, where as forked codebases rarely are able to apply updates to the original code to their modified version.

If the server could accept scripted modules, you wouldn't have to recompile, or take the system offline.

The scripted modules could add skills, races, commands, events, etc.

The codebase just needs to create hooks where the scripted code would effectively be added in. Then people can drop in modules. The modules could be activated and deactivated on the fly from immortals/admins.

Such a server/codebase might revitalize the scene a good deal. I'd love to see it.
USA #25
I'd also like to see all of the hard-coded text moved out of the source code for two reasons.

Let's say that there is code for currency. The code has a bank system with specific menus. The code refers to "gold" as the currency. If someone wants to make a sci-fi mud, they might alter the code and recompile. Again, we develop a fork in the code.

If all the text that appears in the game is moved out of the code to a seperate data file, then not only can we change the theme without altering the core code, but it allows for easy localization/translation without altering the code.
USA #27
I am broke (with new kid) but will try to pick up the book when I can.
USA #28
I also made suggestions here because I'm hoping that Nick wants to continue progress on this. If he is done and wants someone else wants to pick up and run with this, then so be it. If I were a better coder, then I'd love to. Right now I'm fixing up a feature-rich codebase, but if I felt more confident, I'd love to start with something like this and build it up.

My suggestions remain. I'd love to see Nick develop this into something that supportal modular scripts, externalized the text for localization, and added a new combat system.
USA #29
We're already developing the BabbleMUD server. We're in the process of hammering out game design so that we can have a clear idea of where we're going with the code. But chances are, you'll see all this and more in our server.

For instance, we won't just have simple string localization: we'll have a whole grammar module so that localized output is actually done correctly.

You can check out babble.the-haleys.org for a small glimpse of what we're up to. Keep in mind though that that hasn't been updated for quite a while.
USA #30
What did you think of the various combat systems I described?

Honestly, I think my favorite would be a system that allowed for the Aggressive, Defensive, etc. manuevers.

I'll go check out the site.
USA #31
I admit that I didn't read over all of them in full detail yet. I agree that the 'standard' combat system is boring and uninteresting. I'm uneasy about reflex-based combat, though, due to the nature of text-based games. I think you should have to think fast, and adapt your strategy to what your opponent is doing, but that should be through skills and the like, not tapping at the right time on the right button.

For example, I rather like approaches like Dark Age of Camelot's where combat keeps on going without you clicking anything, but you can use special abilities depending on what's going on.

I think this is somewhat similar to aggressive/defensive/etc. stances. But I would have not only choosing a stance, but also choosing which skills to reflect your stance with. The two could conceivably be independent, that is, you could use typically defensive skills while using an aggressive stance. Or the two could be tied together. Either way, I like the idea of having combat stances. :)
USA #32
I read over the webpage, and I'm impressed!

The quick version of my combat idea would be one of the following:

Create a Rock/Paper/Scissors type combat system. A trumps B which trumps C which trumps A...

Or you could do something like...

Aggressive (+X power / -X defense)
Precise (+X to-hit / -X speed)
Defensive (+X defense / -X power)
Quick (+X speed / -X to-hit)

I have lots of questions for your project. Should I post them in this thread? I can't post on the forum there because I don't have an account. I don't know if I need to create an account in the mud to do so, but I can't at the moment because of the firewall here at work.
USA #33
Rock-paper-scissors is hard to model in what we have planned. Loosely speaking we're planning on having a classless system, so it's hard to pre-define what combinations of skills will be available. In fact, there are C(n,m) combinations, where n is the number of skills and m is the number of skills one can learn. ( C(n,m) = (n!)/(m! * (n-m)!), IIRC )

Those stances are the kind of thing we would have. They're pretty good and add interesting styles of play, especially since we plan on having speed + other attributes matter quite heavily.

For questions, as you noted our forum is currently private. It uses Nick's software, so I'll have to talk to him about what kind of access control we have. I'd like to have a public section, for reasons exactly like this one. :)

It'd probably be better to not post them on this thread. Perhaps make a new thread under MUDs | General, or give us a moment to work out the access rights and we'll set up a public section for the BabbleMUD forum.
USA #34
The Rock/Paper/Scissors thing I had in mind had nothing to do with classes.

Let's say there are three type of stances.

Type A can be Crane Style.
Type B can be Mu Shu Style
Type C can be Kentucky Fried Style.

A gives a bonus against B which gives a bonus against C, etc.

But honestly, I prefer the Aggressive, Quick, etc. method. My only concern with that is that players might always use the same attack (Aggressive, whatever). The idea is to vary combat.
USA #35
Oh, I see. But it seems that in that case, combat would be perpetually switching your style to match the weakness of the opponent's style. I imagine that such a thing could be done with triggers, which would turn it into a bot-fight of sorts.

I think that stance should depend on the situation. In some cases, you might want to hit hard, and not worry too much about your own health -- e.g. if you have a healer with you. But in others (e.g. without a healer) you might want to stay alive longer, chipping away at your opponent. And it also depends on your opponent: a wizard will womp you will spells but if you win the fight quickly enough, you'll end it before the powerful spells have a chance to launch.
USA #36
Each round when you take your action, the mob is also choosing A/B/C. So each round you really are playing a guessing game of rock-paper-scissors as it were. 1 in 3 chance for bonus, no bonus, or penalty.

I'm putting together a bunch of questions and suggestions that I'll throw at you later when you decide where you want them.
Amended on Tue 21 Feb 2006 01:29 PM by Enderandrew
USA #37
Oh... ok. :) Again I misunderstood. That certainly sounds novel. How would skills and the like fit in?

Thanks for putting together the questions. I'm waiting to hear from Nick about the forum, and I'll let you know as soon as we figure something out.
USA #38
A public section of the Babble forum would be quite nifty. :)
USA #39
Any word?

Would it be okay for me to start a thread here, or do you think you might get a public forum soon?
USA #40
I haven't heard from Nick yet (since he's not posting here I assume he's away or busy). Go ahead and start a thread here; we'll move it to a Babble forum if/when it gets set up.
#41

The Roshambo combat setup that Ender is describing was used in Horizons...didn't really care for it. The stances that Ksilyan is describing are a lot like warrior stances in WoW. I much prefer the WoW system.

WoW further specializes by having certain skills only available in certain stances. So, you do have to change every once in awhile, even if your primary role is in one stance.
USA #42
"WoW further specializes by having certain skills only available in certain stances. So, you do have to change every once in awhile, even if your primary role is in one stance."

I think that sounds like a really awesome idea!
Amended on Sat 25 Feb 2006 11:57 PM by Nick Gammon
Australia Forum Administrator #43
Quote:

A public section of the Babble forum would be quite nifty


What about the Wiki?
USA #44
The Wiki? Even better :)
USA #45
Where is the Wiki?
USA #46
At the moment it's also private, but we're talking about whether or not to make it public. It'll be either that or the forum, in any case. :)
USA #47
Any news on what/when anything will be made public in the ways of BabbleMUD?
#48
I'm trying to go from using the tinymudserver and write my own server based on that.

I am not sure how much trouble it will be, but I thought I'd give it a shot. I know C code pretty well, but for some reason I ran into a hard time when reading from the player file, I am simply trying to add three lines that read in strings for example

this is a detailed description.

However it doesn't read it in properly, here is my load function, any ideas?

void tPlayer::Load ()
{
ifstream f ((PLAYER_DIR + playername + PLAYER_EXT).c_str (), ios::in);
if (!f)
throw runtime_error ("That player does not exist, type 'new' to create a new one.");

// read player details
f >> password;
f >> room;
getline(f, shortDescr);
getline(f,longDescr);
getline(f,detailedDescr);
f >> level;
for( int i = 0; i < NUM_STATS; i++)
{
f >> stats[i];
}
} /* end of tPlayer::Load */
Australia Forum Administrator #49
Can you post the error messages please?
#50
There is no error message. Its simply not writing or reading from the player file correctly. After you log into the game, if you type save, and I look at the pfile, it has everything stored correctly up to the detailed description, then it just starts writting numbers. I'm not sure why, if it sucessfully stores the short and long descriptions, I would think detailed would work the same.
Australia Forum Administrator #51
In that case you need to post your "save" function, as you say it hasn't saved correctly.
#52
void tPlayer::Save ()
{
ofstream f ((PLAYER_DIR + playername + PLAYER_EXT).c_str (), ios::out);
if (!f)
{
cerr << "Could not write to file for player " << playername << endl;
return;
}
// write player details
f << password << endl;
f << room;
f << shortDescr << endl;
f << longDescr << endl;
f << detailedDescr << endl;
f << level << endl;
for( int i = 0; i < NUM_STATS; i++)
{
f << stats[i] << endl;
}

f << endl;

} /* end of tPlayer::Save */
USA #53
You need to write a newline after 'room'. Do that by adding "<< endl" after "<< room".
#54
I am getting the following error compiling the split version under the latest stable g++ (4.1.1)

utils.h: In function âvoid LoadSet(std::ifstream&, T&)â:
utils.h:26: error: cannot convert âstd::basic_ifstream<char, std::char_traits<char> >â to âchar**â for argument â1â to â__ssize_t getline(char**, size_t*, FILE*)â

Managed to get through several other errors, but I am stumped here (new to C++ but have programmed C and PHP)
USA #55
What is the line in question?
#56
As it says, the line in question is line 26 of utils.h (in the version where Nick separated everything out).

The surrounding area is:

// load a set of flags from a single line in the input stream "f"
template <class T>
void LoadSet (ifstream & f, T & s)
{
s.clear ();
string sLine;
getline (f, sLine); // get one line (THIS THROWS THE ERROR)
istringstream is (sLine); // convert back to stream
string flag; // each flag name
while (!is.eof ()) // read that stream
{
is >> flag; // read flag
s.insert (flag); // and insert it
} // end of getting each item
} // end of LoadSet
Amended on Sun 29 Oct 2006 11:47 PM by Arew264
USA #57
I thought the syntax for getline was...
f.getline(string,SIZE);
Australia Forum Administrator #58
Change the line:


void LoadSet (ifstream & f, T & s)


to:


void LoadSet (istream & f, T & s)
Australia Forum Administrator #59
These are the changes I had to make to get it to compile under the more recent gcc compiler:


diff -c tinymudserver/player.cpp tinymudserver.fixed/player.cpp
*** tinymudserver/player.cpp	Tue Jul 27 15:53:07 2004
--- tinymudserver.fixed/player.cpp	Mon Oct 30 08:03:13 2006
***************
*** 20,25 ****
--- 20,27 ----
  #include <fstream>
  #include <iterator>
  
+ #include <errno.h>
+ 
  using namespace std; 
  
  #include "utils.h"
diff -c tinymudserver/player.h tinymudserver.fixed/player.h
*** tinymudserver/player.h	Tue Jul 27 15:53:07 2004
--- tinymudserver.fixed/player.h	Mon Oct 30 08:02:17 2006
***************
*** 81,87 ****
      {     
      outbuf += MAKE_STRING (i); 
      return *this; 
!     };  
    
    void ClosePlayer () { closing = true; }    // close this player's connection
    
--- 81,87 ----
      {     
      outbuf += MAKE_STRING (i); 
      return *this; 
!     }
    
    void ClosePlayer () { closing = true; }    // close this player's connection
    
diff -c tinymudserver/strings.cpp tinymudserver.fixed/strings.cpp
*** tinymudserver/strings.cpp	Tue Jul 27 15:53:07 2004
--- tinymudserver.fixed/strings.cpp	Mon Oct 30 08:02:39 2006
***************
*** 31,37 ****
        pos = found + replacement.size ();
        }
    return str;
!   };   // end of FindAndReplace
  
  // get rid of leading and trailing spaces from a string
  string Trim (const string & s, const string & t)
--- 31,37 ----
        pos = found + replacement.size ();
        }
    return str;
!   }   // end of FindAndReplace
  
  // get rid of leading and trailing spaces from a string
  string Trim (const string & s, const string & t)
diff -c tinymudserver/utils.h tinymudserver.fixed/utils.h
*** tinymudserver/utils.h	Tue Jul 27 15:25:10 2004
--- tinymudserver.fixed/utils.h	Mon Oct 30 08:12:52 2006
***************
*** 8,25 ****
  struct DeleteObject
    {
    template <typename T>
!   void operator() (const T* ptr) const { delete ptr; };
    };
  // similar concept for maps
  struct DeleteMapObject
    {
    template <typename T>
!   void operator() (const T item) const { delete item.second; };
    };
  
  // load a set of flags from a single line in the input stream "f"
! template <class T>
! void LoadSet (ifstream & f, T & s)
  {
    s.clear ();
    string sLine;
--- 8,25 ----
  struct DeleteObject
    {
    template <typename T>
!   void operator() (const T* ptr) const { delete ptr; }
    };
  // similar concept for maps
  struct DeleteMapObject
    {
    template <typename T>
!   void operator() (const T item) const { delete item.second; }
    };
  
  // load a set of flags from a single line in the input stream "f"
! template <typename T>
! void LoadSet (istream & f, T & s)
  {
    s.clear ();
    string sLine;



You can send the above through the patch program to apply them automatically, or just visually do it. The lines I changed are in bold above. The file names and line numbers are part of the "diff" text. Basically I had to remove a few semicolons, add a couple, add an include file, and change "ifstream" to "istream".
Amended on Sun 29 Oct 2006 08:40 PM by Nick Gammon
#60
My output seems very skewn when I connect to the server.


Welcome to the Tiny MUD Server version 2.2.0

It is displayed as follows :


http://img405.imageshack.us/my.php?image=1uy4.jpg


Any ideas guys?
Amended on Sat 30 Dec 2006 01:20 PM by Radnire
USA #61
No offense, but why in the world are you using telnet to connect to a MUD? As it happens, your choice of client is why the output is screwed up because it fails to correctly interpret the formatting info.

Try again with MC or another of the many clients designed for MU*s and let us know if the formatting is still off.
#62
For what it's worth I wouldn't normally, HOWEVER the readme clearly states

"CONNECTING

The default behaviour is to listen for connections on port 4000 (change a constant in
the code to alter this). To test the server you could connect to it like this:

telnet localhost 4000"

So I assumed that it would at least be compatible with telnet. Assumed that seen as it was the method described in the Readme it might even be the 'preferred' method. Silly me.
Australia Forum Administrator #63
Yes I admit the readme says that. The reason for the strange formatting is that the server is sending linefeeds (hex 0x0a) rather than carriage-return/linefeed (hex 0x0d 0x0a) pairs, because most clients these days only need the linefeed to start a new line.

It wouldn't take a huge effort to do a simple find-and-replace of one to the other, for example in the comms output routine.

I probably tested it with MUSHclient, but to save suggesting that my own client was needed I put in the line "To test the server you could connect to it ... using telnet".
#64
All good then, your right after testing in several mud clients it seems to be formatted just fine, and as you say should be easy to accommodate telnet clients too if the need arises.

One other thing I noticed whilst browsing the code, the DoTell function seems to have a mistake in it :

string what = GetMessage (sArgs, "Tell " + p->playername + " what?"); // what
*p << "You tell " << p->playername << ", \"" << what << "\"\n"; // confirm

This prints the teller's name instead of the recipients name. For example if player Vicki sends a message to player John it would look like this :

__Vicki__
tell John Hello
You tell Vicki, "Hello"

__John__
Vicki tells you, "Hello"


Should be a simple fix if there is support for the targets name, haven't looked yet (it was 4:50am when I looked at this and was just casually browsing whilst waiting for emails to download)

Anyway just thought I'd let it be known, if it isnt already.
#65
For those who care the two lines mentioned should be around line 180 in commands.cpp

I have not looked at the code surrounding ptarget yet but I would assume you could simply fix this by changing the p->playername to ptarget->playername so your new lines would look like this:

string what = GetMessage (sArgs, "Tell " + ptarget->playername + " what?"); // what
*p << "You tell " << ptarget->playername << ", \"" << what << "\"\n"; // confirm

Someone let me know if this isn't possible, I'll try it out when I get home from work tonight, hope it's as easy a fix as I think :)
Australia Forum Administrator #66
You are probably right, there will be bugs like that in it.

The tiny server was really an illustration of how to get people to connect, and some example commands. It wasn't extensively tested, and turning it into a full server will take quite some work.
#67
Would you perhaps know of any other barebone type servers/codebases?

In C/C++/VB/Java perhaps?
Australia Forum Administrator #68
A modified version which has successfully compiled under gcc 3.4.4 is now available for downloading:

http://www.gammon.com.au/files/muds/tinymudserver_v2_3.tgz

Its md5sum is: 2c0fb4f7ecff50708bbd713376c2ae6f

Basically it is the same as tinymudserver_v2_2.tgz but with the minor changes described earlier in this thread applied, so that various error messages about semicolons, etc., are removed.
Australia Forum Administrator #69
Quote:

Would you perhaps know of any other barebone type servers/codebases?


I think someone has done a Lua one. The one presented here is in C++.

Check out this page:

http://www.andreasen.org/newmud/
#70
Hooray thread revival!

Boo compilation errors.

Having issues getting Tiny to compile under Dev-C++
Holy have an error list

no socket header file, popped winsock2 in its place, which brought about an unbalanced #ENDIF error. commented ot the last #ENDIF and that part at least compiles now..

no <sstream>, replaced it with <strstream>, which added a brand new assortment of errors..

Is there any way this will compile cleanly under Dev-CPP without serious editing, or should I just bite the nugget and install Cygwin?
I'd really like to be able to run this from a thumb drive without threatening tenderloin slappings to get Cygwin installed at school)
Amended on Sun 30 Sep 2007 06:07 AM by The Illustrious Skwerl
Australia Forum Administrator #71
I don't know about the idiosynracies of Dev-C++, so it is probably easier to install Cygwin. To run it you probably only need the .exe file plus the cygwin DLL whose exact name I don't know because I am not at my usual PC.
USA #72
Hooray thread revival! Again!

I prefer Mingw32, available http://www.mingw.org/
Compiles c,c++ code that works under Cygwin with very few changes. Mingw32 itself is derived from Cygwin.
#73
AFAIK, the compiler in Dev-C++ is MinGW.
#74
I've been working on using TinyMUDserver version 2 for a while now to create my own from-scratch MUD - I'm a MUD developer myself, with a few years of experience now. I picked this project up largely because I'm sick of the horribly convoluted projects that are out there now, with dozens and dozens of authors over multiple decades.

The reason I was attracted to tinymud was that basically the only part of writing a MUD I didn't know how to do was the telnet connectivity part of things - it's one of those implemention details that I had never cared to touch, as it was just assumed to work fine and didn't really need any new developments.

One thing I would like to do for my MUD though is add in extended character support. I did not think TinyMUD had extended character support, as it tended to convert its strings back into char * at the very last stages of sending to a descriptor - but actually testing this fact, I found that it does in fact allow for extended characters, however, the character immediately after the extended character is always capitalized.

For example:

The user sends the text 'Für' to the Mud, it will display this as 'FüR', or 'Große' becomes 'GroßE'. As a contrived example, I tested 'Ex¼mple', which returns 'Ex¼Mple'.

Without having looked too deeply into this issue (which I do intend to do), I was wondering if anybody else had looked into this issue? I noticed that there has been at least some previous discussion of multilanguage support for MUDs, especially from Nick Gammon himself, so I figured if this is an issue, it has probably been run into before, and I was wondering if anybody knows the fix.

If I discover the fix, I will post about it here.
Australia Forum Administrator #75
I assume you are using tocapitals somewhere, as that has a bit of a bug.

In strings.cpp look for the function fCapitals, and inside that is:


    // work out whether next letter should be capitals
    bUpper = isalnum (c) == 0;


That test won't really work for extended characters, as it will fail the test 'isalnum'. It should probably read something like:


    // work out whether next letter should be capitals
    bUpper = isalnum (c) == 0 && c < 0x80;


There is no particular problem with converting to char * for extended characters, as that only fails in one case, which is 0x00, however extended characters are >= 0x80.

Admittedly it technically should probably be unsigned char *, however when casting, it doesn't actually change the underlying characters.

#76
Thanks for the advice Nick, it worked like a charm. I simply had assumed that extended characters didn't gel very well with c-strings as a previous project I had worked on had merely discarded them, and I didn't quite understand how the extended character sets could possibly be stored in a char.

Consequently, top notch job on Tinymud. I took tinymudserver v2 and stripped out basically everything until it was essentially just keeping track of logins, and handling input (though the only commands were quit and shutdown). I've written everything else with Object Orientation in mind, and I even had a polymorphic series of classes for characters, though I have since taken a different approach as my other developers (mostly schooled in java) didn't really understand it.

Decided to run with xml to save things, as it seems to be all the rage. It's very freeing to be working with a MUD architecture that uses proper STL libraries, streams, and actually takes advantage of C++ features rather than being a dodgy diku hack.

One question I had though - where did you learn about writing all that connectivity stuff? I mean, that's essentially what I (and probably others) are using Tinymudserver for. I've not found a lot of information on it myself.
Australia Forum Administrator #77
I think I looked at Diku derivatives, and PennMUSH, in their comms handling sections, which gave the general idea of what to do. Then I read the manual about creating sockets, doing a listen, accept, etc. making sure I knew exactly why every thing was done in a certain way.

I think a lot of comms code is "take a copy of an existing one because it seems to work" - and potentially duplicating problems with obscure situations. For example, an error code returned on a read, or a write.

It also helps to find a book or web site about the theory about TCP/IP so you understand what is happening behind the scenes. Couple that with a packet sniffer so you can watch the packets fly back and forwards as you establish a connection.
#78
Has anyone thought of adding Lua to TinyMud? Picked up the Ron Penton book and TinyMud is better IMO than the BETTERmud he has to use as a core. It has a strange quirk of not letting you repeat direction commands.
Australia Forum Administrator #79
TinyMUD is pretty minimal - I think I actually did a Lua-based server a little while after that. Not sure where the source is though.
USA #80
i think i have big picture in my head that your $home directory is messy.. is it? :) be organized next time! :p

in anycase(sorry for being off-point), Your LuaMud look cool and i has good idea and experiment upon it for while.

tiny MUD server look good too, but i wonder how do I embed lua within the code.

~maxhrk~
Poland #82
Hey!
How can I create new rooms?
Australia Forum Administrator #83
Bear in mind this server is really a tutorial, rather than a working MUD.

However to answer the question, in a directory "rooms" in the download is a file "rooms.txt". In that are the room definitions.

Judging by that file the layout is:


<room number>
<description>
<exits>


For example:


1000
Starting room (room 1000).%rYou are standing in a fabulous, fabulous, exotic room, with all sorts of things that go 'hum' and 'plink' in the night.%r%rAs you glance around the room, you wonder what else might exist in this world.
n 1004 s 1001 e 1002 w 1003
1001
Room 1001.%rYou are in room 1001. It could look better. What a let-down! This rooms has no alabaster walls, no mystic fireplace, no marble floors.
n 1000


Thus room number 1000 goes north to 1004, south to 1001, east to 1002, and west to 1003.

You can also see that you put linebreaks in room descriptions with %r.
Poland #84

Thanks for the code - now I can see clearly where is my mistake - I forgot to write the room number.

Now it works!

Starting room (room 1000).
No i nie wiesz co robi&#263;. Chcesz wyj&#347;&#263; na zewn&#261;trz?
Exits: e n s tak w 
> tak
You go tak
Komnata (room 1006).
Wielka sala,
a - wró&#263; do pokoju 1000.
b - id&#378; do pokoju 1001.
Exits: a b 


Thanks for Your answer!
Australia #85
Hi there,

I tried compiling this with g++ 4.3.2 and seem to be hitting a snag:

strings.cpp: In function ‘std::string tolower(const std::string&)’:
strings.cpp:51: error: ‘transform’ was not declared in this scope
strings.cpp: In function ‘std::string tocapitals(const std::string&)’:
strings.cpp:85: error: ‘transform’ was not declared in this scope

I assume this has something to do with ctype.h?

Also, I noticed the STL includes toupper() and tolower() functions, so I thought you'd be able to just call that with arguments, but you've implemented your own functions tolower() and tocapitals(), using the transform() function. Is this correct, and if so, why?

Please remember I'm looking at this through newbie coloured glasses, and my interpretation of what's going on could be completely ridiculous to someone skilled in coding.

Thanks for any help.

edit: I managed to solve this myself, yay! Although it was completely without skill, I just searched for the functions in google and saw what includes they used in example code, then threw them into my code, these are the changes required for it to compile:

strings.cpp
#include <algorithm>
player.cpp
#include <algorithm>
#include <limits>
load.cpp
#include <limits>
comms.cpp
#include <algorithm>

Would there have been a more logical way to solve this problem? As I might not always have internet access, I'd like to be more self sufficient as opposed to relying on google and your brains for answers.

Cheers
Amended on Tue 18 Aug 2009 12:12 PM by Trask
USA #86
Quote:
strings.cpp: In function ‘std::string tolower(const std::string&)’:
strings.cpp:51: error: ‘transform’ was not declared in this scope
strings.cpp: In function ‘std::string tocapitals(const std::string&)’:
strings.cpp:85: error: ‘transform’ was not declared in this scope


I assume this has something to do with ctype.h?
<snip>
Would there have been a more logical way to solve this problem? As I might not always have internet access, I'd like to be more self sufficient as opposed to relying on google and your brains for answers.

That was the correct solution. When something in a standard library isn't found, there's a 99.9% chance that the header file wasn't included. Things were moved around in various releases of the STL, different compiler versions, and different implementations handle things differently, which is why this code compiled for Nick but didn't compile for you.

Quote:
Also, I noticed the STL includes toupper() and tolower() functions, so I thought you'd be able to just call that with arguments, but you've implemented your own functions tolower() and tocapitals(), using the transform() function. Is this correct, and if so, why?

I think you probably mean the standard C library, not the STL itself. The toupper and tolower functions work on one character at a time. Therefore, to work on an entire string, you transform each character of the string (one character at a time) by passing it through the appropriate function.
Australia #87
That was the correct solution. When something in a standard library isn't found, there's a 99.9% chance that the header file wasn't included. Things were moved around in various releases of the STL, different compiler versions, and different implementations handle things differently, which is why this code compiled for Nick but didn't compile for you.


--------------------------------------------------------------------------------


I know each linux distro puts different things in different places, but is there a general area in which I could have used grep to search for the offending function? That way, I wouldn't have to rely on the internet.
USA #88
/usr/include is where the files typically live. The problem is that the STL files go in all kinds of places underneath that. For example, on my Ubuntu system, I have this:


$ pwd
/usr/include/c++
$ find . -name algorithm
./4.1.3/algorithm
./4.1.3/ext/algorithm
./4.3/parallel/algorithm
./4.3/algorithm
./4.3/ext/algorithm
$ 


As you can see, the 'algorithm' header file exists in several places. So if you're finding something in the STL, I'd recommend first finding where your STL files live -- hopefully something like the above. Then you can grep around in there.

Another option is to install the library documentation from your package manager; then you can use 'man' to bring up the docs which usually tell you what the header is. (Unfortunately, the documentation is sometimes sketchy for STL features.)

Finally, you can use old-fashioned references like the Josuttis STL book that Nick and I both use & like.

After a while, though, you'll probably just start including the entire list of STL headers that you've needed. Sometimes I have stl.h that includes all the necessary STL header files. (To avoid compilation slowdowns, I use precompiled headers.)
Australia #89
Thanks for the help David,

I didn't even realise I could install documentation, I'll do that at my next opportunity. I'm reading Bruce Eckel's Thinking in CPP atm, but I might have to order the Josuttis book as a progression.

Thanks again mate!
Australia Forum Administrator #90
I found it somewhat hard to get the answer quickly, so I sympathize. :)

I couldn't find it in the manual, even after installing stl-manual.

Eventually a Google for "transform" gave the answer:

http://www.sgi.com/tech/stl/transform.html

Then I hit:


player.cpp: In member function ‘void tPlayer::Load()’:
player.cpp:82: error: ‘numeric_limits’ was not declared in this scope
player.cpp:82: error: expected primary-expression before ‘int’


I had to:


#include <limits>


into a couple of files.

Oh well, this is what happens with old code. The header files used to cross-reference each other, and someone tightened it up.
#91
I just got this to compile on g++ 4.5.0.

Changes needed:

#include <algorithm>
in several files.
#include <limits>
in several files.
#include <errno.h>
in several files.
Australia Forum Administrator #92
Thanks Hex.

Sounds like I better do an improved version that works out of the box. :-)

That's not a criticism BTW, its just that compilers keep evolving, and things that used to work, suddenly don't.
Amended on Mon 16 Aug 2010 11:25 AM by Nick Gammon
Australia Forum Administrator #93
The Tiny MUD server is now available for downloading from GitHub:

http://github.com/nickgammon/tinymudserver

The latest commit (f3cb4f0) fixes the problems with the missing include files.

It now compiles OK for me under:


gcc (Ubuntu 4.3.2-1ubuntu12) 4.3.2



You can clone a read-only version from GitHub like this:


git clone git://github.com/nickgammon/tinymudserver.git


#94
I'm having trouble creating a command that lists the current flags that a character has.

I'd rather just cycle through and spit out each flag, rather than checking for each flag like this:

if (HaveFlag (can_shutdown))
if (HaveFlag (can_goto))


That just seems unnecessary.
USA #95
Why not something like:

for flag in flag list:
  if ch has flag:
    print "ch has flag '" + flag + "'"
Australia Forum Administrator #96
From the source, where it saves the player flags:


 copy (flags.begin (), flags.end (), ostream_iterator<string> (f, " "));


A variation on that should do it, as that was all it took to write them to the player file.
Australia Forum Administrator #97
This worked for me, there might be an easier way:


void DoShowFlags (tPlayer * p, istream & sArgs)
{
  tPlayer * ptarget = p->GetPlayer (sArgs, "Usage: showflags <who>");  // who
  NoMore (p, sArgs);  // check no more input

  *p << "Flags for " << ptarget->playername << " : ";

  // loop through each flag  
  for (std::set<string, ciLess>::const_iterator it = ptarget->flags.begin ();
       it != ptarget->flags.end ();
       it++)
     *p << *it << " ";
  
  *p << "\n"; 
      
} // end of DoShowFlags

#98
So I am experimenting with putting in color in various ways and since there was already a nice piece of code written to replace the %r with \n I thought I would play with it.


      messagemap [tolower (sMessageCode)] = FindAndReplace (sMessageText, "%r", "\r\n");

      messagemap [tolower (sMessageCode)] = FindAndReplace (messagemap [tolower (sMessageCode)], "[/color]", "\e[0m");


Someone already asked the question that gave me the part of the code that replaces [/color] - But what I don't quite understand is why the replacement works with the second piece of the code but if I try to use the first piece it doesn't.


      messagemap [tolower (sMessageCode)] = FindAndReplace (sMessageText, "%r", "\r\n");

      messagemap [tolower (sMessageCode)] = FindAndReplace (sMessageText, "[/color]", "\e[0m");


Why doesn't this work?
#99
Thank you for the DoShowFlags piece of code, I had given up for the time being.

I hope I'm not coming off as if I'm asking questions before experimenting and using some google. Because that certainly is not the case.

I'm trying to get a feel for this base and I think I mostly have it. The rest, I think, will be easy. ;)
Netherlands #100
Mudder said:

... But what I don't quite understand is why the replacement works with the second piece of the code but if I try to use the first piece it doesn't.


      messagemap [tolower (sMessageCode)] = FindAndReplace (sMessageText, "%r", "\r\n");

      messagemap [tolower (sMessageCode)] = FindAndReplace (sMessageText, "[/color]", "\e[0m");


Why doesn't this work?


Because strings are immutable, and what you are assigning on the left side is not the same thing as you are using on the right side. When programming, do not forget that it is a 'timeline' from top to bottom; it is not math where everything is a statement of truth that is supposed to happen at the very same time.

To put it in a bit more trivial example without all the extra syntax cruft...

my_lucky_number = 42                      -- MY lucky number is 42.
your_lucky_number = my_lucky_number + 1   -- YOUR lucky number is mine plus one (43).
your_lucky_number = my_lucky_number - 10  -- YOUR lucky number is mine minus ten (32).
my_lucky_number = 1                       -- MY lucky number is 1.


You changing your mind on your lucky number does not change the fact that I still trust in the Hitchhiker's Guide of the Galaxy upto that point... until I get immersed into numerology. Then I choose one. But you still are stuck with 32 as your lucky number, since that is what you decided at that point in time.
USA #101
Quote:
Because strings are immutable

Not in all languages, and in particular, not in C++. I think Mudder knows that program flow is linear. I don't know why it's not working since there is missing context and I don't know what the expected result is.

Mudder, you said that the first one doesn't work but the second one does, but then you ask why the second one doesn't work -- could you be more precise about which one is the one you expect to work but doesn't?
Netherlands #102
David Haley said:

Quote:
Because strings are immutable

Not in all languages, and in particular, not in C++.


Oops. Seems I am totally guilty of misinformation here. For some reason, I saw the code snippet, and after reading other topics involving Lua, I assumed it was Lua. My bad. :)

Still, assuming FindAndReplace does not modify the string in-place (which would be weird given the fact it returns stuff), the rest of my post holds.
Amended on Mon 04 Oct 2010 01:55 AM by Worstje
#103
I suppose I wasn't entirely clear. Worstje did actually help me a little.

I've been away from coding for awhile so I was forgetting some basic things. I assumed tolower was modifying the original, instead of returning another. Newbie mistake!

Originally I couldn't see why the code wasn't working. I posted a working copy (the first code snip)

First Snip


messagemap [tolower (sMessageCode)] = FindAndReplace (sMessageText, "%r", "\r\n");

messagemap [tolower (sMessageCode)] = FindAndReplace (messagemap [tolower (sMessageCode)], "[/color]", "\e[0m");



then I posted a second snip and hoped for an explanation as to why it didn't work as the first did.

Second Snip


messagemap [tolower (sMessageCode)] = FindAndReplace (sMessageText, "%r", "\r\n");

messagemap [tolower (sMessageCode)] = FindAndReplace (sMessageText, "[/color]", "\e[0m");


The second part of both snips was where the question was.

The answer, of course, was that the original string wasn't being modified so I just kept reassigning values to the messagemap.

Since then I've been working to find a way to write this in a better way, slimming it down as much as possible. This is what I have.

void LoadHelps ()
{
  // format is: <code> <message>
  //  eg. motd Hi there!
  // Imbedded %r sequences will becomes new lines.
  ifstream fHelps (HELPS_FILE, ios::in);
  if (!fHelps)
    {
    cerr << "Could not open helps file: " << HELPS_FILE << endl;
    return;
    }

  while (!(fHelps.eof ()))
    {
    string sMessageCode, sMessageText;
    fHelps >> sMessageCode >> ws;
    getline (fHelps, sMessageText);
    if (!(sMessageCode.empty ()))
      sMessageText = FindAndReplace (sMessageText, "%r", "\r\n");
      sMessageText = FindAndReplace (sMessageText, "[/color]", "\e[0m");
      sMessageText = FindAndReplace (sMessageText, "[red]", "\e[0;31m");
      sMessageText = FindAndReplace (sMessageText, "[green]", "\e[0;32m");
      sMessageText = FindAndReplace (sMessageText, "[yellow]",  "\e[0;33m");
      sMessageText = FindAndReplace (sMessageText, "[blue]", "\e[0;34m");
      sMessageText = FindAndReplace (sMessageText, "[magenta]", "\e[0;35m");
      sMessageText = FindAndReplace (sMessageText, "[cyan]", "\e[0;36m");
      sMessageText = FindAndReplace (sMessageText, "[white]", "\e[0;37m");
      sMessageText = FindAndReplace (sMessageText, "[grey]", "\e[1;30m");
      sMessageText = FindAndReplace (sMessageText, "[RED]", "\e[1;31m");
      sMessageText = FindAndReplace (sMessageText, "[GREEN]", "\e[1;32m");
      sMessageText = FindAndReplace (sMessageText, "[YELLOW]", "\e[1;33m");
      sMessageText = FindAndReplace (sMessageText, "[BLUE]", "\e[1;34m");
      sMessageText = FindAndReplace (sMessageText, "[MAGENTA]", "\e[1;35m");
      sMessageText = FindAndReplace (sMessageText, "[CYAN]", "\e[1;36m");
      sMessageText = FindAndReplace (sMessageText, "[WHITE]", "\e[1;37m");
      messagemap [tolower (sMessageCode)] = sMessageText;

    } // end of read loop
} // end of LoadHelps


The above shows the context, the other snips were simply the bottom half.

[EDIT] Edited by Nick to put in some backslashes where required.
Amended on Mon 04 Oct 2010 03:09 AM by Nick Gammon
Australia Forum Administrator #104
Mudder said:

I'm having trouble creating a command that lists the current flags that a character has.


Here is an improved version. It took a bit of research to work out how to write an output iterator for a player, but I managed it. :)



// put this in player.h

template <typename T>
class player_output_iterator : public std::iterator <std::output_iterator_tag, void, void, void, void>
{
 protected:
   tPlayer &    player_;  // who we are outputting to
   const char * delim_;   // delimiter between each item
   
 public:
  // constructor
  player_output_iterator (tPlayer & p, const char* d = "")
    : player_ (p), delim_ (d) {}
  
  // copy constructor
  player_output_iterator (const player_output_iterator<T>& rhs)
   : player_ (rhs.player_), delim_ (rhs.delim_) {}
  
  // assignment
  player_output_iterator<T>& operator= (const T& rhs)
    { 
    player_ << rhs << delim_;  
    return *this;
    }
  
  // dereference - no operation, returns reference to itself
  player_output_iterator<T>& operator* ()     { return *this; }
  // increment   - no operation, returns reference to itself
  player_output_iterator<T>& operator++ ()    { return *this; }
  // increment   -  no operation, returns reference to itself
  player_output_iterator<T>& operator++ (int) { return *this; }
};  // end of player_output_iterator


// this is in commands.cpp

void DoShowFlags (tPlayer * p, istream & sArgs)
{
  tPlayer * ptarget = p->GetPlayer (sArgs, "Usage: showflags <who>");  // who
  NoMore (p, sArgs);  // check no more input
  *p << "Flags: for " << ptarget->playername << " : ";
  copy (ptarget->flags.begin (), ptarget->flags.end (), player_output_iterator<string> (*p, " "));
  *p << "\n"; 
      
} // end of DoShowFlags



Basically the output iterator is a class that gets instantiated in the copy call (line in bold). It remembers in its internal variable which player we want (the target of the output), and then implements operator= for assignment, so that each time the copy algorithm copies a string to the iterator, it then gets copied the the player, with the delimiter after it.

This might be lengthier now, but lets you iterate over other things, and copy other batches of stuff (inventory maybe) to the player, so it is a neater end-result.
Amended on Mon 04 Oct 2010 03:29 AM by Nick Gammon
Australia Forum Administrator #106
The thing that bothers me a bit about the output iterator on the previous page is that it has the tPlayer type hard-coded into it. A bit of juggling and you can make an output iterator for any type:


template <typename CharT>
class generic_output_iterator : public std::iterator <std::output_iterator_tag, CharT, void, void, void>
{
 protected:
   CharT      & item_;     // item we are outputting to
   const char * delim_;    // delimiter between each thing output
   
 public:
  // constructor
  generic_output_iterator (CharT & c, const char* d = "")
    : item_ (c), delim_ (d) { }
  
  // copy constructor
  generic_output_iterator (const generic_output_iterator & rhs)
    : item_ (rhs.item_), delim_ (rhs.delim_) { }
  
  // assignment - outputs to item_, followed by delim_
  template <typename Tp>
  generic_output_iterator & operator= (const Tp & rhs)
    { 
    item_ << rhs << delim_;  
    return *this;
    }  // end of operator=
  
  // dereference
  generic_output_iterator & operator* ()     { return *this; }
  // pre increment
  generic_output_iterator & operator++ ()    { return *this; }
  // post increment
  generic_output_iterator & operator++ (int) { return *this; }
};  // end of generic_output_iterator


typedef generic_output_iterator<tPlayer> player_ostream_iterator;


void DoShowFlags (tPlayer * p, istream & sArgs)
{
  tPlayer * ptarget = p->GetPlayer (sArgs, "Usage: showflags <who>");  // who
  NoMore (p, sArgs);  // check no more input
  *p << "Flags for " << ptarget->playername << " : ";
  std::copy (ptarget->flags.begin (), ptarget->flags.end (), player_ostream_iterator (*p, " "));
  *p << "\n"; 
      
} // end of DoShowFlags


The templated class generic_output_iterator now makes an output iterator for any type. The output type is automatic because that is templated inside the iterator.

Then the line in bold creates a player_ostream_iterator specifying the class to output to (tPlayer). Then the copy function in DoShowFlags can be simpler because we no longer need to specify that we are sending a string to the iterator.

[EDIT] Tidied up a bit to take an automatic output stream type (eg. string).
Amended on Mon 04 Oct 2010 08:43 PM by Nick Gammon
#107
Howdy. I wasn't sure where to post this, so I posted it here. I've downloaded Tiny Mud from GitHub, but I ran into a compile error that I don't know how to correct. Below is the log from my terminal when I typed "make". Can anyone help?

kyle@ubuntu:~/Downloads/tinymudserver$ make
g++ -g3 -Wall -w -pedantic -fmessage-length=0 -c tinymudserver.cpp
g++ -MM tinymudserver.cpp > tinymudserver.d
g++ -g3 -Wall -w -pedantic -fmessage-length=0 -c strings.cpp
g++ -MM strings.cpp > strings.d
g++ -g3 -Wall -w -pedantic -fmessage-length=0 -c player.cpp
g++ -MM player.cpp > player.d
g++ -g3 -Wall -w -pedantic -fmessage-length=0 -c load.cpp
g++ -MM load.cpp > load.d
g++ -g3 -Wall -w -pedantic -fmessage-length=0 -c commands.cpp
g++ -MM commands.cpp > commands.d
g++ -g3 -Wall -w -pedantic -fmessage-length=0 -c states.cpp
g++ -MM states.cpp > states.d
g++ -g3 -Wall -w -pedantic -fmessage-length=0 -c globals.cpp
g++ -MM globals.cpp > globals.d
g++ -g3 -Wall -w -pedantic -fmessage-length=0 -c comms.cpp
comms.cpp: In function 'int InitComms()':
comms.cpp:104: error: 'perror' was not declared in this scope
comms.cpp: In function 'void ProcessNewConnection()':
comms.cpp: 211: error: 'perror' was not declared in this scope
comms.cpp:219: error: 'perror' was not declared in this scope
make: *** [comms.o] Error 1

Amended on Fri 22 Oct 2010 03:26 PM by Kyle Chappetta
#109
Hi Nick.

Is this server ipv6 compatible? If it isn't, how I can add support for ipv6?
Amended on Thu 13 Mar 2014 02:35 AM by Martin4503
#110
I hope there's still someone here. :)

I'm wondering if anyone ever coded a "look (thing)".

In the commands.cpp file I see this:

void DoLook (tPlayer * p, istream & sArgs)
{

// TODO: add: look (thing)

I realy need the ability to look at a 'thing' that I've defined in the rooms.txt file as a new line after the exits.

It would save me a lot of time if anyone has a snippet or two for me.

Thanks!!
Jack
Australia Forum Administrator #111
I got your message on Facebook but I don't want to have technical discussions there. :)


To look at an object first objects (things) need to exist (which they don't as I recall). So you would need to make a datatype for an object, then make it so they can be in a room, or in someone's inventory (or a mob's inventory).

You would pick up an object by removing it from the list in the room and adding it to the list in the inventory. Dropping it would be the reverse process.

To get you started with the syntax of looking, you can modify DoLook to see if there is an argument (eg. "look sword") and if so pass control to another function that looks at that thing. Example code below.


void lookObject (tPlayer * p, string & which)
  {
  *p << "Looking at object " << which << "\n";

  // scan available objects and display information about them ...

  }  // end of lookObject

/* look */

void DoLook (tPlayer * p, istream & sArgs)
{
 
  // look (thing)

  string whichObject;
  sArgs >> ws >> whichObject;

  if (!whichObject.empty ())
    {
    lookObject (p, whichObject);
    return;
    }
    
  
  // find our current room, throws exception if not there
  tRoom * r = FindRoom (p->room);
  
...


I warn you that this sort of stuff can get complex quite quickly. For example, you probably need a "template object" (eg. a sword) and then an instance of that object (a particular sword held by a particular mob). Also you have to modify the game load/save code to remember their inventory.