Embedded Color How-To
CircColor EMBEDDED COLOR for CIRCLEMUD
Author: M.C. Lewis
-------------------------------------------------------------------------------
NOTE: This should work, but I may have left something out of the
instructions. If so, check the website to see if the corrected
version is up; if it isn't, drop me a note. I pulled some existing
code, stripped it, looked at default Circle and looked at where I had
made changes to another version of Circle and tried to correlate.
THESE INSTRUCTIONS ARE FOR INSTALLING COLOR INTO CIRCLEMUD 3.0 bpl 11
-------------------------------------------------------------------------------
e-mail: mc2@geocities.com
W W W :
http://www.geocities.com/SiliconValley/Park/6028
(also the home of CircEdit)
NOTE: There are patches included with the distribution for people
who are modifying a DEFAULT CIRCLEMUD. See patch-readme.txt
for information. It takes about 30 seconds with the patches, 5 minutes if you
follow these instructions and do it by hand.
NOTE: I saw something called "easy color v2" in the CircleMUD FTP
incoming directory the other day. I got it as soon as it was moved and looked at what had been done
to my code (I am the original Easy Color author). It still didn't range-check, and it had no cursor
control, plus it didn't have directions for colorizing things other than the normal text, so I went
ahead with putting up this version. I wrote some conversion programs to change Easy Color
\c## codes and "Easy Color 2.2" &? Codes to CircColor codes. I added a flashing text control
to CircColor for compatibility (I had left it out purposely) and now recommend you use
CircColor instead of Easy Color. You can consider this "Easy Color 3" if you like.
This version is pretty complete,
and supports CircEdit color codes by default (or vice-versa),
so you don't need to change either to make it work with the other.
INTRODUCTION
You can use color in CircleMUD by using embedded color codes. In any text
that gets sent to the character, whether room descriptions, prompts, pager
strings (like "news"), etc., color codes are replaced by ANSI colors.
INSTALLING this code is pretty staightforward and should only take a few
minutes. It can be accomplished by anyone who can operate the "Find" or
"Search For" capability of a text editor. Not to be rude, but I have
gotten mail from people who seem to be incapable of that. I have tried
to be VERY SPECIFIC with these instructions.
These instructions may look pretty long, but that's because I've included
a lot of text from Circle for reference. It's actually a short process.
Here are the colors:
/cl Black
/cr Red
/cg Green
/cy Yellow
/cb Blue
/cp Purple (magenta) same as /cm
/cc Cyan
/cw White (looks grey in normal terminals, use /cW for white)
Use a capital letter for the extended (bold or light) version of the color,
such as /cR /cB /cY /cW etc. In a color_xterm under Xwindows, the normal
colors are correct and extended are bold; in a normal terminal (not X),
normal colors are dark and extended colors are light. For instance,
/cy is rather brownish and /cw is grey, while /cY is yellow and /cW is white.
/b followed by a letter (lowercase only) turns on background color.
/c0 turns off ALL color. /b0 turns off background color but keeps text
whatever color it was.
Cursor controls are
/Cc Clear screen, cursor home
/Ch cursor home
/Cu cursor up
/Cr cursor right
/Cl cursor left
/Cd cursor down
Style Commands are
/cu Underline (on Xterminals)
/cf Flashing text (on normal terminals)
/cv Reverse mode (switch fg and bg settings)
/co Foreground off (background stays the same) (normal terminals)
NOTE ON COLOR SEQUENCE:
When using background colors, you can specify background colors
and EXTENDED ANSI foreground colors in any order. For example,
/cW/bb Extended White text on blue background
AND
/bb/cW Extended White text on blue background
Will both work correctly. However, when using NORMAL foreground
colors, you need to specify the foreground color first. This is because the
ANSI code for normal colors automatically turns off the background color.
For example, /cw/bb works correctly but /bb/cw is just normal white text,
no background.
ALSO note that Xterminals behave a bit strangely with background colors, if
you have the background on and send a newline (\r\n), you'll get a filled-in
background character on the next line. Sometimes they also extend the background
to the width of the screen if they encounter a newline and background color is on.
OTHER CHARACTERS:
// is a normal forward slash.
You can change / to anything you want by changing START_CHAR in the code.
If you make it a backslash ( which is \ ), you can use \cr in text files
but your .c files will need TWO slashes, \\cr since C uses backslash as a
special character. For instance, strcpy(name,"\\cBCircleMUD");
This would mean you need THREE backslashes to output a backslash to a player.
That is, strcpy(string,"This is a backslash: \\\");
My advice: Don't use a backslash. Also, use something that you don't normally
use, like / or ^ or |, so that lines like "You don't see a magazine/clip
here" don't all have to be changed. YES, with the default forward slash,
that line wouldn't come out correctly. You'd need "magazine//clip"
instead. I have included programs in the distribution which will double-slash
your files for you, should you decide to use the forward slash and not want to
change your files by hand.
*** YOUR CHOICE =) ***
INSTRUCTIONS
SO FAR, SO GOOD? Okay, the easy part:
1. Save this file as color.c in your CircleMUD's src/ directory
2. In your makefile, where you get a huge list of object files, add in color.o, e.g.,
OBJFILES = comm.o color.o act.comm.o act.informative.o act.movement.o act.item.o \
act.offensive.o act.other.o act.social.o act.wizard.o ban.o boards.o \
castle.o class.o config.o constants.o db.o fight.o graph.o handler.o \
house.o interpreter.o limits.o magic.o mail.o mobact.o modify.o \
objsave.o olc.o shop.o spec_assign.o spec_procs.o spell_parser.o \
spells.o utils.o weather.o
3. At the end of the makefile, add in these lines:
color.o: color.c
$(CC) -c $(CFLAGS) color.c
after weather.o. That is,
weather.o: weather.c conf.h sysdep.h structs.h utils.h comm.h handler.h \
interpreter.h db.h
$(CC) -c $(CFLAGS) weather.c
color.o: color.c
$(CC) -c $(CFLAGS) color.c
Make sure you're using a single tab for indentation, not spaces.
NOTE that color.c doesn't depend on any header files in CircleMUD. This means
you should only need to compile it ONCE! Every time you monkey around with
screen.h, you have to recompile anything that uses those CC COLOR macros.
You can tinker around with this file all you want, and only recompile IT
and then just re-link. Saves time.
Now for the SLIGHTLY MORE DIFFICULT PART:
Okay, you've added color.c to your Makefile. Now the code's there, but
Circle's gotta call it.
In comm.c,
you need to include the screen.h file.
comm.c: After these:
#include "structs.h"
#include "utils.h"
#include "comm.h"
#include "interpreter.h"
#include "handler.h"
#include "db.h"
#include "house.h"
add:
#include "screen.h"
We will be using a macro defined in screen.h, that's why we need the
include. You could add screen.h to the comm.o dependencies, but this
isn't necessary for our purposes.
Now we need to add the function declaration for our function,
proc_color, in color.c.
In the file comm.c, locate
/* extern fcnts */
near the top, and then
AFTER
void weather_and_time(int mode);
ADD
void proc_color(char *inbuf, int color_lvl);
WHICH SHOULD BE BEFORE
/* *********************************************************************
* main game loop and related stuff *
********************************************************************* */
And you're done with step four. For the next step, we're going to make
it so prompts can be colored. In the function
make_prompt, at the top:
void make_prompt(struct descriptor_data *d)
{
char prompt[MAX_INPUT_LENGTH];
Add in these two lines right after that:
int clvl=0;
if(d->character) clvl=COLOR_LEV(d->character);
Those two lines are going to be at the top of most functions we're going
to change.
Okay, now find:
if (PRF_FLAGGED(d->character, PRF_DISPMOVE))
sprintf(prompt, "%s%dV ", prompt, GET_MOVE(d->character));
strcat(prompt, "> ");
write_to_descriptor(d->descriptor, prompt);
And add in this call, right before the write:
proc_color(prompt,clvl);
So now it looks like this:
strcat(prompt, "> ");
proc_color(prompt,clvl); /* THIS IS WHAT WE JUST ADDED */
write_to_descriptor(d->descriptor, prompt);
Now you're done with make_prompt. Your prompts will be colored.
The next step is to make the NORMAL output colored. Normal text you read
(room descriptions, extra descriptions, pager info (like news, motd, etc.))
go through this next function.
Find the function process_output:
int process_output(struct descriptor_data *t)
{
static char i[LARGE_BUFSIZE + GARBAGE_SPACE];
static int result;
At the top, where the variables have been declared, we need the same
lines as before, except with a t instead of d for the descriptor name,
so add these lines in right after the above lines:
int clvl=0;
if(t->character) clvl=COLOR_LEV(t->character);
Go down a little ways, and find
/*
* now, send the output. If this is an 'interruption', use the prepended
* CRLF, otherwise send the straight output sans CRLF.
*/
if (!t->prompt_mode) /* && !t->connected) */
result = write_to_descriptor(t->descriptor, i);
else
result = write_to_descriptor(t->descriptor, i + 2);
Right after the comment, and right before the line
if (!t->prompt_mode) /* && !t->connected) */
Add in the line
proc_color(i,clvl); /* in color.c */
This is what actually processes the input buffer. It will take whatever
string you give it, add in the colors/cursor controls, and copy it back
over the original string.
Those lines now look like
proc_color(i,clvl); /* in color.c */
if (!t->prompt_mode) /* && !t->connected) */
result = write_to_descriptor(t->descriptor, i);
else
result = write_to_descriptor(t->descriptor, i + 2);
RESTRICTING COLOR:
If you want to disallow players using color codes, all you need to do is
call proc_color with color level set to zero. This will strip out all
color codes the player may have written. You can then continue processing
that string normally.
NOTE that although proc_color doesn't range-restrict shorter strings (it
only cuts off after the max buffer length), you still won't end up with
a string too long. WHY? Because by the time you call proc_color, you've
already made sure the string is within the acceptable length, and calling
it with color level set to zero will strip out any color codes, making it
SHORTER.
The call to do this should be placed in game_loop.
NOTE: You may need to do checking in case you end up with a null string,
I haven't checked. That is, if the character simply enters a color code
which gets filetered out, the input buffer is now '\0'. This probably
won't change anything but I haven't actually checked, since I didn't
filter out player colors in CircleMUD. As far as I know, this is treated
just like hitting RETURN by itself.
In game_loop:
void game_loop(int mother_desc)
Go way down and look for this line:
if ((--(d->wait) <= 0) && get_from_q(&d->input, comm, &aliased)) {
Right after, simply add
proc_color(comm,0);
To filter out all color codes. If you want to restrict by level, or age,
or nose size, or what-have-you, put in a check like:
if(d->character) /* has character */
if(GET_LEVEL(d->character) < MY_MINIMUM_COLOR_LEVEL___WHATEVER_IT_IS)
proc_color(comm,0);
Instead.
FIXING THE PAGER COUNTING FUNCTION:
All right, now here's the slightly trickier part. In order for the
pager to count lines corectly, it needs to know about your embedded codes.
It already knows about ANSI codes, but what we're doing is handing it
codes BEFORE they're turned into ANSI strings. An easy way out is to
simply proc_color the buffer before we start counting pages, but alas,
it won't count correctly if there are any cursor controls in the pager
text. Of course, there shouldn't be, but if there are and we process it
and THEN count pages, it will have problems since it expects to see an "m"
at the end of anything started with an escape character.
Still, there are a couple of ways to do this:
1. Process it all and then count pages WHILE CHECKING for the end of any
escape sequence that doesn't end in "m", which also allows variable-
length codes, or
2. Count pages while checking for any color code (starting in / and
exactly 3 characters long).
You want to do it the first way in case you add color codes that add
printable text; for instance, were you to implement the measurement codes
(declared but not implemented here) you could have a line like:
"The giant stands /m19F tall."
Which, depending on the user's preference, is output as either:
The giant stands 19 feet tall.
-- OR --
The giant stands 6 meters tall.
You probably won't have a line like that in a text read with the pager,
but say you add a word-wrap feature to your mud; you would then need to
know the PRINTABLE length of the output string exactly, and would need
to process it first.
It's shorter to do it the second way, although it means you'll need to
know the character you used as the START_CHAR in color.c. By default,
it's the forward slash /, but if you change that, you'll need to change it
here, too.
I cut out the instructions for how to do it the first way because it's pretty long and
takes extra memory, since you have to make a copy of every string for every character.
I'm including the instructions for the shorter method, since it's much easier and lets you
use a single copy of game texts (like NEWS) to be shared among players (which is the way
the pager NORMALLY does it) rather than cloning it and doing special processing in
advance for EVERY player each time a text is accessed.
----METHOD #2:----
In modify.c, function next_page:
/* Traverse down the string until the begining of the next page has been
* reached. Return NULL if this is the last page of the string.
*/
char *next_page(char *str)
{
Look for this:
/* Check for the begining of an ANSI color code block. */
else if (*str == '\x1B' && !spec_code)
spec_code = TRUE;
And right after, add this:
/* Check for the beginning of a color code */
else if (!spec_code && (*str == '/') && !((*(str+1))=='/'))
spec_code = TRUE;
Lower down, look for this:
/* Check for the end of an ANSI color code block. */
else if (*str == 'm' && spec_code)
spec_code = FALSE;
And add this:
/* Check for the end of a color code */
else if ( spec_code && ( (*(str-2) ) == '/' ) )
spec_code = FALSE;
And you're done. Make sure that the '/' is the same as START_CHAR from
color.c
(change it to whatever character you're using for START_CHAR).
----------------------
NOTE that this doesn't actually change the settings in the pager; we
just tell it to pretend the codes have a printable length of zero.
If you'd like to use cursor-control formatting in your pager texts, and
want it to keep track of rows/columns correctly, I am leaving that as an
exercise for the reader. I will give a quick hint, though:
-----> In this same function (next_page), where you see
/* Check for everything else. */
else if (!spec_code) {
/* Carriage return puts us in column one. */
if (*str == '\r')
col = 1;
/* Newline puts us on the next line. */
else if (*str == '\n')
line++;
You'll need to change it so if you have a spec_code, and it's cursor up, then
line = line -1; If it's a cursor home, then col is 1 and line is 1; etc.
*******ONE MORE THING ABOUT THE PAGER:
If you're using color codes, put the color code at the beginning of
each line. You don't know where your text is going to get cut, so you
need lines like
/cWThis is my news file. Boy, it looks good.
/cWTry new veggie ham, veggie fish, veggie burgers, veggie beef,
/cWveggie pork chunks, and /cr(NEW)/cR *VEGGIE HUMAN*!
/cWNatives from Borneo took the /cBPEPSI CHALLENGE/cW and couldn't tell
/cWour veggie human from the real thing 9 times out of 10!
with the color code at the beginning of each line to ensure it all gets
colored correctly. E.g., if you start using white, and later on start
using green, then the person pages back, your text which should be white
will appear as whatever the current cursor color is (probably green).
THUS, you need color codes at the beginning of every line, not just at
the beginning of sections.
Don't worry about redundant codes getting sent, proc_color remembers
the current color and won't append an ANSI string if you're already
using that color. Only new ones get sent. This is noticeable with slower
modem connections.
PAGER PROMPT:
YOU WILL ALSO want to either append a color off (/c0) to the end of
each line of text (not so hot), or to the beginning of your pager
prompt, so that the pager prompt is not colored the same as your text.
Or, if you want to color your pager prompt some other color, that'll
work, too. I don't recommend the /c0 at the end of each line, so let's
see how to change the pager prompt instead.
***The short way (simply turning off color for your pager prompt):
In comm.c, function make_prompt, look for:
sprintf(prompt,
"\r[ Return to continue, (q)uit, (r)efresh, (b)ack, or page number (%d/%d) ]",
d->showstr_page, d->showstr_count);
write_to_descriptor(d->descriptor, prompt);
And simply insert a "\E[0;0m" before the \r of the string, i.e.,
"\E[0;0m\r[ Return to continue,"...................etc.
That will turn off color.
*** To USE COLOR CODES, do the following (same function):
Right before the write_to_descriptor line, add
proc_color(prompt, clvl);
Remember we have clvl from our previous modifications to pager prompt.
Also, add another slash in (%d/%d) and add a "/c0" to the beginning.
So now it looks like:
sprintf(prompt,"/c0"
"\r[ Return to continue, (q)uit, (r)efresh, (b)ack, or page number (%d//%d) ]",
d->showstr_page, d->showstr_count);
proc_color(prompt,clvl);
write_to_descriptor(d->descriptor, prompt);
You can now make your pager prompt look nice. Example:
sprintf(prompt,"/cW/bb"
"\r[ Return to continue, (q)uit, (r)efresh, (b)ack, or page number (%d//%d) ]/c0",
d->showstr_page, d->showstr_count);
This will use a blue background with white text.
---------- THE TITLE SCREEN ----------
Some people have an ANSI-graphical text file they use for the screen.
This already has the ANSI codes in the file. If you want to use THESE
COLOR CODES IN YOUR TITLE SCREEN, HOWEVER, YOU NEED TO FOLLOW THESE
STEPS:
Note that in all our changes, we set the variable clvl to 0, then checked
to see if the descriptor had a character associated with it, and if so,
set the color level to the character's color level preference setting.
WHEN YOU FIRST LOG ON, THERE IS NO CHARACTER ASSOCIATED WITH YOUR
DESCRIPTOR, so you need to process your string with color turned on.
In comm.c, in the function new_descriptor, we need to add space for a
string, then process it and output it. Look for:
int new_descriptor(int s)
{
socket_t desc;
int sockets_connected = 0;
unsigned long addr;
int i;
static int last_desc = 0; /* last descriptor number */
struct descriptor_data *newd;
struct sockaddr_in peer;
struct hostent *from;
extern char *GREETINGS;
After these lines, add
char titlescreen[LARGE_BUFSIZE]; /* or whatever size is appropriate */
Then look for
SEND_TO_Q(GREETINGS, newd);
Remove it, replacing it with the following lines:
strcpy(titlescreen, GREETINGS); /* copy fixed-sized string to larger
buffer */
proc_color(titlescreen, C_CMP); /* C_CMP should be 3 */
SEND_TO_Q(titlescreen, newd);
You can now change
GREETINGS in
config.c to use color codes and now you've got a
colored title screen!
****************************************************************************
That should about do it. I hope you don't have too much trouble
with it. =)
****************************************************************************