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.
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.
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).
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).
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
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
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
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).
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.
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:
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 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++.
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:
Valid directions (n, s, e, w etc.)
Disallowed new player names
Banned IP addresses
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 ...
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.
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.
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?
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.
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...
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.
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.
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.
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... :-)
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.
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.
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. :)
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.
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.
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.
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.
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. :)
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.
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.
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.
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.
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.
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.
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.
"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'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 */
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.
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;
}
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)
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
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".
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.
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.
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".
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.
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 :)
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.
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.
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)
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.
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.
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.
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.
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.
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.
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.
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.
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ć. Chcesz wyjść na zewnątrz?
Exits: e n s tak w
> tak
You go tak
Komnata (room 1006).
Wielka sala,
a - wróć do pokoju 1000.
b - idź do pokoju 1001.
Exits: a b
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:
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.
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.
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.
/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:
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.)
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.
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.
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.
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.
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.
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?
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.
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)
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.
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).
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
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.