TIPS - Some helpful hints and examples for implementing cname 1.3. (by stratocaster) ------------------------------------------------------------------ Silver once told me that I was one of the few people he knew of who had taken the time to install his version of cname, and implement it in more places than what the diff file handles. After having installed cname 4 times, I think I understand why. Since PG+ was written by several different people, all with different coding styles, it can be hard to understand where to put calls to get_cname(), and even harder to determine which variables go inside the parenthesis. Being a somewhat experienced coder myself, I hate to see good code go to waste, and I hate to see a good coder spend a lot of his own personal time coding something, only for it to not be used. So hopefully, by trying to explain things in more detail, and by giving some examples, I can help everyone who wants to implement cname to understand just how it works. Before we get started, please note that all the examples are taken directly from the code of my own talker. It should work on your talker, too... but if it doesn't, I'm not responsible in any way. Alrighty! If you haven't already done so, take a moment now to read the bottom of Silver's README file, paying special attention to the section entitled "How to finish the code:". This is VERY important. If you don't understand this, you'll have a rather hard time installing cname. Let's take a look. get_cname(p, q) where: p = the pointer to the player whose name you wish to display q = the pointer to the player who you are showing the name to Seems simple enough, doesn't it? The first thing that confused me about this was the p and q part. Very rarely in the code will you find an instance with p and q. You may have p, p2, scan, start, current_player, 0, or any of a multitude of other variable names. This is a good example of what I said earlier, about PG+ being written by lots of different people with different styles. But don't fret... all you really need to remember is that the first variable is the person whose name you are displaying, and the second variable is the name of the person (or people) you're showing that name to. So, let's look at a simple example. Open up admin2.c in your favorite text editor, and jump to the end of "void assist_player", where you'll see this: TELLPLAYER(p, " You assist %s.\n", p2->name); LOGF("assist", "%s assists %s", p->name, p2->name); Here's a good example of where and where not to add cname. In the TELLPLAYER function, the first argument is always the pointer to the player that the message is being told to. The second argument is always the message being told. Any other arguments are variables that fit into the message. In this case, the message "You assist <name>." is being told to player p. <name> would be the name of the person being assisted. If we compare this to the original example, (the p's and q's) it's pretty simple to figure out. We should add a get_cname here, where p2 is the name being displayed, and p is the person that p2's coloured name is being displayed to. To put it simply, we'd rewrite it like this: TELLPLAYER(p, " You assist %s.\n", get_cname(p2, p)); Make sense? I hope so. ;) Now, onto that LOGF call. If you notice, it's got names in the message too. But in this case, it's probably better that we don't add cname. We know whose name is being displayed in this case... both p and p2... but we can't predict who is going to be seeing those names. So, whenever you see a ->name in a LOGF call, just skip it and continue merrily on your way. Just because you can't immediately tell who the name is being displayed to doesn't always mean you shouldn't add cname. If you're not sure, take a moment to read through the entire function, and try to follow its logic. Often, you can find clues that will help you determine who will be seeing the name. For our first example of this, let's have a look at "void check_idle" in commands.c. This is a fairly large chunk of code, so I'm going to cut out a few sections, to make this example a little bit shorter. void check_idle(player * p, char *str) { player *scan, **list, **step; int i, n; char *oldstack, middle[80], namestring[40], *id; file *is_scan; int page, pages, count, not_idle = 0; [snip] One important thing to note here. Since we know that only players have coloured names, we can discount all the other variables when implementing the get_cname() function. In the above bit, you can see that we have 4 different "players" here: p, scan, *list, and *step. Don't worry about the *'s too much for now, just note that they're there. oldstack = stack; command_type |= SEE_ERROR; if (isalpha(*str) && !strstr(str, "everyone")) { align(stack); list = (player **) stack; #ifdef ALLOW_MULTIS n = global_tag(p, str, 0); #else n = global_tag(p, str); #endif if (!n) { stack = oldstack; return; } id = stack; for (step = list, i = 0; i < n; i++, step++) { if (p->custom_flags & NOPREFIX) { strcpy(namestring, (*step)->name); } else if (*((*step)->pretitle)) { sprintf(namestring, "%s %s", (*step)->pretitle, (*step)->name); } else { strcpy(namestring, (*step)->name); } if (!(*step)->idle) { sprintf(stack, "%s has just hit return.\n", namestring); } else { if ((*step)->idle_msg[0]) { sprintf(stack, "%s %s\n%s is %s idle\n", namestring, (*step)->idle_msg, caps(gstring((*step))), word_time((*step)->idle)); } else { sprintf(stack, "%s is %s idle.\n", namestring, word_time((*step)->idle)); } } stack = end_string(stack); tell_player(p, id); stack = id; } Well, we finally got to the tell_player function, and we can see that in this instance, the message is being told to player p. But where's the message!?!? Let's backtrack. We can see here that the player is being told something named "id". What is "id", you ask? Well, according to the line below tell_player, "stack = id". Okay, seems simple enough so far. So... we need to look for where someone's name is printed to stack. Going back up a few lines, we see that a message is printed to the stack, and that the first variable is "namestring". That sounds pretty close, but it's not in ->name format, and it's not one of the players that we identified at the beginning of the check_idle function. So, we go back up a little more, and we find 3 instances where (*step)->name is copied or printed to "namestring". It may look a little odd, but it fits what we're looking for. It's in ->name format, and *step is one of our players for this function. So, these three instances are where we need to add calls to get_cname(). And since we now know which name is being displayed, and who will be seeing it, this should be fairly simple. The first instance: strcpy(namestring, (*step)->name); Should become: strcpy(namestring, get_cname(*step, p)); Okay, so that was pretty heavy. Feel free to take a break, re-read some stuff if you need to, because it's only going to get stranger from here. If you don't understand things up to this point, you're going to get even more lost from here on out... so go over those examples again, find a few functions of your own that are similar to these situations. The best way to learn is to practice! So far, we've looked at players p, p2, and *step. I'd like to take a moment to go over another common player pointer that you'll see quite often. For this example, we're going to look at "list_whos_on_channel", in dsc.c: void list_whos_on_channel(player * p, char *channel) { player *scan; int counted = 0, i; char *oldstack = stack, temp[70]; sprintf(temp, "People on the '%s' channel", channel); pstack_mid(temp); for (scan = flatlist_start; scan; scan = scan->flat_next) for (i = 0; i < MAX_CHANNELS; i++) if (!strcasecmp(scan->channels[i], channel)) { counted++; if (scan->dsc_flags & BLOCK_ALL_CHANS) stack += sprintf(stack, "(%s), ", scan->name); else stack += sprintf(stack, "%s, ", scan->name); } if (counted) { stack = end_string(stack); oldstack[strlen(oldstack) - 2] = '.'; oldstack[strlen(oldstack) - 1] = '\n'; tell_player(p, oldstack); tell_player(p, LINE); } else tell_player(p, " No-one on the channel ?!? *le boggle*\n"); stack = oldstack; } This time, we've got a new player on the scene, "scan". Just as in the last example, we've got a tell_player() function, where something called "oldstack" is being told to player p. We see that "stack = oldstack" in the last line, so we go back a few lines, where we see that scan->name is being printed to the stack. So we've got our three elements... where to put the get_cname call, the player being told to, and the player's name being displayed. In this case, there's more than one player's name being told to player p, but this function goes through all the players one at a time, so each time it gets a player, that name becomes scan->name. When the loop completes, and the function goes into the next loop, scan->name becomes the name of the next person on the list... and so on. So, while scan is actually a lot of different players, we can (in this instance) treat it as if it were just one name. Having said that, you should be able to connect the rest of the dots. This line: stack += sprintf(stack, " %s, ", scan->name); becomes: stack += sprintf(stack, " %s, ", get_cname(scan, p)); So now we've covered pretty much all the different player types I mentioned in the beginning. There's only one left, and it can be the trickiest of the bunch. Deciding when to use it often takes a bit of looking around at the function you're working on, and thinking about how that function is used by the program, and ultimately, by the people logged in. This mysterious player I'm talking about is "current_player". It can be pretty complex in some situations, but in the context we're using it in, it basically refers to any or all people viewing the message. Let's take a look at "void connect_channels" in dsc.c: void connect_channels(player * p) { char *oldstack = stack; int i; if (p->dsc_flags & BLOCK_ALL_CHANS) return; for (i = 0; i < MAX_CHANNELS; i++) if (*(p->channels[i])) { sprintf(stack, "[%s] ++ %s logs in and joins this channel ++\n", p->channels[i], p->name); stack = end_string(stack); tell_channels(p->channels[i], oldstack); stack = oldstack; } } Going by the guidelines we've used up to this point, we see that we've got player p, and that's the only player listed. Instead of tell_player, we've got tell_channels, but it looks similar enough at first glance. A message is being put on the stack, and then passed via tell_channels, and we've got a p->name... but there's something missing! We've only got one player! Who are we telling this message to? Eek! Panic attack! Okay, maybe not. Take a look at the message that's being displayed: "[<some channel>] ++ <name> logs in and joins this channel ++ Think about that for a second. Who would be seeing this message? Anyone who's currently on <some channel> when player p connects to the talker. This could be one person, or nobody, or lots of people. So how do we know? Well, that's where current_player comes in. Since current_player refers to any or all people viewing a message, it seems that this would be a good place to use it. Hence, this line: sprintf(stack, "[%s] ++ %s logs in and joins this channel ++\n", p->channels[i], p->name); becomes: sprintf(stack, "[%s] ++ %s logs in and joins this channel ++\n", p->channels[i], get_cname(p, current_player)); So now, you may be thinking, "If current_player refers to the person or people viewing the message, why not use it everywhere?" Well, it's not that simple. With TELLPLAYER and tell_player, you are specifically sending a message from the program to ONE person. With functions like tell_channels, tell_room, and TELLROOM, you could only be telling one person, but you could be sending the message to a lot of people. If you were sending a message to one specific person, using current_player would be a bad idea in general, and just wouldn't work in other places. Let's look at another instance of where you would need to use current_player, and I'll demonstrate a point that Silver made in the README file. Take a look at "void eat_item" in items.c. Again, I'm going to snip out some of the code here, 'cause I just noticed how long this file is getting. ;) void eat_item(player * p, char *str) { item *i; struct s_item *s; [snip] else { TELLROOM(p->location, "%s munches happily on a delicious looking %s.\n", p->name, s->name); TELLPLAYER(p, "You smile as you eat the delicious %s in one bite. Yum!\n", s->name); [rest snipped] Now, here we've got a TELLROOM, and a TELLPLAYER. Let's look at the room function first. We've got p->name and s->name. Oddly, s is in the ->name format, but it doesn't seem to be of type player. Which means we only have one player: p. If we stop to think about it for a moment, this is the message that's displayed to the entire room when someone eats an edible item. So, to put some colour into it: TELLPLAYER(p->location, "%s munches happily on a delicious looking %s.\n", get_cname(p, current_player), s->name); But what about that crazy s!?! Doesn't it get colour too? It sure doesn't. Remember what Silver said in the README about not changing every single instance of ->name? This is one of those. Like I said earlier, it's NOT a player. Actually, s->name, in this case, is the name of a saved item. Since only players have coloured names, we merely skip this and pretend it wasn't there. ;) While we're talking about that part of README, I think it's worthwhile to note that all the instances of get_cname() so far have been used in tell_player, TELLPLAYER, TELLROOM, and similar function calls. Like Silver said, you should only modify ->name bits if they are displayed to a player. As this file has gotten pretty long, I'm going to touch on one more point before I finish. One of the major problems I had when installing cname was that it messed up preformatted lists. What are preformatted lists? Take a look at 'lsu' on your talker when there's more than one staff member on. See how the columns line up so nicely? Well, simply adding cname in those lists causes everything to get knocked out of whack, and sometimes it even cuts off parts of people's names. Let's have a look at a chunk of code from "void lsu" in admin2.c: for (scan = flatlist_start; scan; scan = scan->flat_next) { prestack = stack; if (scan->location && scan->residency & PSU) { if (p->residency & PSU && *str != '-') { count++; *stack = ' '; stack++; sprintf(stack, " %-18s ", scan->name); stack = strchr(stack, 0); if (scan->saved_residency & CODER) sprintf(stack, "%-18.18s ", get_config_msg("coder_name")); else if (scan->saved_residency & HCADMIN) If you look at the entire function, you'll see that the call here should be "get_cname(scan, p)". But, because the spacing is calculated before the colour codes are converted, it makes quite a mess of things. So, after some brainstorming, I came up with the following: sprintf(stack, " %-18s ", scan->name); becomes: sprintf(stack, " %-*s ", ((MAX_NAME - strlen(scan->name)) + strlen(get_cname(scan, p))), get_cname(scan, p)); I don't know if that's the best way to do it or not, but it works fairly well; I've put that in just about every bit of code on my talker that uses cname with a "%-18s" (or some number other than 18), and it's worked every time. You're welcome to use that, but like I said at the beginning, don't blame me if it doesn't work. Well, I guess that's about it for now. I hope that helps. If there are any questions, feel free to email me at stratocaster@notnet.co.uk, but please try to keep your questions specific. Good luck! -- Will Fischer 11/02/01