/**************************************************************************
* Original Diku Mud copyright (C) 1990, 1991 by Sebastian Hammer, *
* Michael Seifert, Hans Henrik St{rfeldt, Tom Madsen, and Katja Nyboe. *
* *
* Merc Diku Mud improvements copyright (C) 1992, 1993 by Michael *
* Chastain, Michael Quan, and Mitchell Tse. *
* *
* In order to use any part of this Merc Diku Mud, you must comply with *
* both the original Diku license in 'license.doc' as well the Merc *
* license in 'license.txt'. In particular, you may not remove either of *
* these copyright notices. *
* *
* Much time and thought has gone into this software and you are *
* benefiting. We hope that you share your changes too. What goes *
* around, comes around. *
***************************************************************************
* ROM 2.4 is copyright 1993-1998 Russ Taylor *
* ROM has been brought to you by the ROM consortium *
* Russ Taylor (rtaylor@hypercube.org) *
* Gabrielle Taylor (gtaylor@hypercube.org) *
* Brian Moore (zump@rom.org) *
* By using this code, you have agreed to follow the terms of the *
* ROM license, in the file Rom24/doc/rom.license *
***************************************************************************
* 1stMUD ROM Derivative (c) 2001-2003 by Ryan Jennings *
* http://1stmud.dlmud.com/ <r-jenn@shaw.ca> *
***************************************************************************/
#include "merc.h"
#define IN_CHANNELS_C
#include "gcn.h"
#undef IN_CHANNELS_C
#include "interp.h"
#include "globals.h"
#include "recycle.h"
#include "tables.h"
#include "lookup.h"
#include "olc.h"
const struct gsn_type gcn_table[] = {
#undef GCN_H
#define MAKE_GCN_TABLE
#include "gcn.h"
#undef MAKE_GCN_TABLE
{NULL, NULL}
};
int *gcn_lookup(const char *word)
{
int i;
for (i = 0; gcn_table[i].name != NULL; i++)
{
if (!str_cmp(word, gcn_table[i].name))
return gcn_table[i].pgsn;
}
return NULL;
}
const char *gcn_name(int *pgcn)
{
int i;
for (i = 0; gcn_table[i].pgsn != NULL; i++)
{
if (pgcn == gcn_table[i].pgsn)
return gcn_table[i].name;
}
return NULL;
}
const char *get_chan_colour(CHANNEL_DATA * chan)
{
if (chan->custom_colour != -1)
return FORMATF(CTAG(%d), chan->custom_colour);
else
return chan->colour;
}
const char *format_channel(CHANNEL_DATA * chan, CHAR_DATA * ch)
{
static char buf[MSL];
strcpy(buf, chan->format);
switch (chan->spec_flag)
{
case spec_clan_flag:
sprintf(buf, chan->format, ch->clan->name,
ch->clan->rank[ch->rank].rankname);
break;
default:
break;
}
strcat(buf, get_chan_colour(chan));
return (buf);
}
bool display_channel(CHAR_DATA * ch, CHAR_DATA * victim, CHANNEL_DATA * chan,
chanarg_t type, bool fShow)
{
if (!ch || !victim)
return FALSE;
if (IS_SET(victim->wiznet, WIZ_CHANSNOOP))
return TRUE;
if (!fShow)
{
if (chan->bit > 0 && IS_SET(victim->comm, chan->bit))
return FALSE;
if ((type == CHANNEL_EMOTE || type == CHANNEL_SOCIAL)
&& IS_SET(victim->comm, COMM_NOGOCIAL))
return FALSE;
if (IS_SET(victim->comm, COMM_QUIET))
return FALSE;
if (is_ignoring(victim, ch->name, IGNORE_CHANNELS))
return FALSE;
}
switch (chan->spec_flag)
{
case spec_clan_flag:
if (!is_same_clan(ch, victim))
return FALSE;
break;
case spec_imm_flag:
if (!IS_IMMORTAL(victim))
return FALSE;
break;
case spec_buddy_flag:
if (victim != ch
&& (check_buddy(ch, victim) == -1 || check_buddy(victim, ch) == -1))
return FALSE;
break;
case spec_public_flag:
return TRUE;
break;
case spec_none:
return FALSE;
}
return TRUE;
}
#if !defined(NO_WEB)
void init_www_history(void)
{
int x;
for (x = 0; x < 20; x++)
{
www_history[x] = &str_empty[0];
}
}
#endif
void init_channel_history(PC_DATA * pcdata)
{
int i, x;
alloc_mem(pcdata->history, const char **, maxChannel);
alloc_mem(pcdata->history_index, int, maxChannel);
for (i = 0; i < maxChannel; i++)
{
pcdata->history_index[i] = 0;
if (channel_table[i].page_length <= 0)
continue;
alloc_mem(pcdata->history[i], const char *,
channel_table[i].page_length);
for (x = 0; x < channel_table[i].page_length; x++)
pcdata->history[i][x] = &str_empty[0];
}
}
void realloc_channel_history(PC_DATA * pcdata, int gcn, int val, int oval)
{
int i, x;
if (gcn >= 0 && gcn < maxChannel)
{
if (oval <= 0)
{
if (val > 0)
{
alloc_mem(pcdata->history[gcn], const char *, val);
}
}
else
{
if (val <= 0)
{
free_mem(pcdata->history[gcn]);
}
else
{
realloc_mem(pcdata->history[gcn], const char *, val);
for (x = oval; x < val; x++)
pcdata->history[gcn][x] = &str_empty[0];
}
}
}
else
{
realloc_mem(pcdata->history, const char **, val);
realloc_mem(pcdata->history_index, int, val);
for (i = oval; i < val; i++)
{
pcdata->history_index[i] = 0;
if (channel_table[i].page_length <= 0)
continue;
alloc_mem(pcdata->history[i], const char *,
channel_table[i].page_length);
for (x = 0; x < channel_table[i].page_length; x++)
pcdata->history[i][x] = &str_empty[0];
}
}
}
void free_channel_history(PC_DATA * pcdata)
{
int i, x;
for (i = 0; i < maxChannel; i++)
{
if (channel_table[i].page_length <= 0)
continue;
for (x = 0; x < channel_table[i].page_length; x++)
free_string(pcdata->history[i][x]);
free_mem(pcdata->history[i]);
}
free_mem(pcdata->history);
free_mem(pcdata->history_index);
}
#if !defined(NO_WEB)
const char *PERS_WWW(CHAR_DATA * ch)
{
if (!ch)
return "@@@";
if (IS_IMMORTAL(ch) && (ch->invis_level != 0 || ch->incog_level != 0))
return "an Immortal";
else if (IS_AFFECTED(ch, AFF_INVISIBLE))
return "someone";
return IS_NPC(ch) ? ch->short_descr : ch->name;
}
#endif
void update_last_data(CHAR_DATA * sender, CHAR_DATA * viewer,
CHANNEL_DATA * channel, const char *str, chanarg_t type)
{
char *time;
int gcn, i;
char buf[MSL];
const char *chan;
if (IS_NPC(viewer) || !channel || channel->page_length <= 0)
return;
gcn = *channel->index;
chan = format_channel(channel, sender);
++viewer->pcdata->history_index[gcn];
viewer->pcdata->history_index[gcn] %= channel->page_length;
i = viewer->pcdata->history_index[gcn];
time = str_time(current_time, GET_TZONE(viewer), "%I:%M:%S %p");
switch (type)
{
case CHANNEL_NORMAL:
sprintf(buf, "[%s] %s{x %s %s '%s'{x", time, chan, PERS(sender,
viewer),
sender == viewer ? "say" : "says", str);
break;
case CHANNEL_SOCIAL:
sprintf(buf, "[%s] %s{x %s{x", time, chan, str);
break;
case CHANNEL_EMOTE:
sprintf(buf, "[%s] %s{x %s %s{x",
time, chan, PERS(sender, viewer), str);
break;
case CHANNEL_THINK:
sprintf(buf,
"[%s] %s %s . o O ( %s ){x", time, chan, PERS(sender,
viewer), str);
break;
default:
bugf("bad channel type [%d]", type);
buf[0] = '\0';
break;
}
replace_string(viewer->pcdata->history[gcn][i], buf);
#if !defined(NO_WEB)
if (channel->spec_flag != spec_public_flag || sender != viewer)
return;
++www_index;
www_index %= 20;
time = str_time(current_time, -1, "%I:%M:%S %p");
switch (type)
{
case CHANNEL_NORMAL:
sprintf(buf, "[%s] %s %s says '%s'", time, chan, PERS_WWW(sender), str);
break;
case CHANNEL_SOCIAL:
sprintf(buf, "[%s] %s %s", time, chan, str);
break;
case CHANNEL_EMOTE:
sprintf(buf, "[%s] %s %s %s", time, chan, PERS_WWW(sender), str);
break;
case CHANNEL_THINK:
sprintf(buf, "[%s] %s %s . o O ( %s )",
time, chan, PERS_WWW(sender), str);
break;
default:
bugf("bad channel type [%d]", type);
buf[0] = '\0';
break;
}
replace_string(www_history[www_index], buf);
#endif
}
void view_last_data(CHAR_DATA * ch, CHANNEL_DATA * chan)
{
int i, gcn;
bool found = FALSE;
BUFFER *output;
if (!chan || chan->page_length <= 0)
return;
output = new_buf();
gcn = *chan->index;
for (i = (ch->pcdata->history_index[gcn] + 1) % chan->page_length;
i != ch->pcdata->history_index[gcn]; i = (i + 1) % chan->page_length)
{
if (!IS_NULLSTR(ch->pcdata->history[gcn][i]))
{
found = TRUE;
bprintln(output, ch->pcdata->history[gcn][i]);
}
}
if (!IS_NULLSTR(ch->pcdata->history[gcn][ch->pcdata->history_index[gcn]]))
{
bprintln(output,
ch->pcdata->history[gcn][ch->pcdata->history_index[gcn]]);
found = TRUE;
}
if (!found)
bprintln(output, "None.");
else
{
bprintlnf(output, "\n\rCurrent time: %s Your Login Time: %s",
str_time(current_time, GET_TZONE(ch), "%I:%M:%S %p"),
str_time(ch->logon, GET_TZONE(ch), "%I:%M:%S %p"));
}
sendpage(ch, buf_string(output));
free_buf(output);
}
void channel_social(CHAR_DATA * ch, CHAR_DATA * victim, OBJ_DATA * obj,
const char *string, CHANNEL_DATA * chan)
{
DESCRIPTOR_DATA *d;
const char *type = format_channel(chan, ch);
for (d = descriptor_first; d; d = d->next)
{
CHAR_DATA *vch = CH(d);
if (vch && (vch != ch) && (vch != victim)
&& display_channel(ch, vch, chan, CHANNEL_SOCIAL, FALSE))
{
if (d->connected == CON_PLAYING)
{
char buf[MSL];
sprintf(buf, "%s %s{x", type, string);
perform_act(buf, ch, obj, victim, FALSE, vch);
}
update_last_data(ch, vch, chan,
perform_act_string(string, ch, obj, victim, FALSE),
CHANNEL_SOCIAL);
}
}
update_last_data(ch, ch, chan,
perform_act_string(string, ch, obj, victim, FALSE),
CHANNEL_SOCIAL);
}
void public_ch(CHAR_DATA * ch, const char *argument, int gcn)
{
char command[MIL + 100];
DESCRIPTOR_DATA *d;
chanarg_t chan_type = CHANNEL_NORMAL;
char arg_left[MSL];
CHANNEL_DATA *chan = &channel_table[gcn];
const char *type;
if (chan == NULL)
{
chprintln(ch, "Channel is currently unavailable.");
return;
}
type = format_channel(chan, ch);
if (IS_NULLSTR(argument))
{
if (chan->bit <= 0)
chprintln(ch, "What do you want to say?");
else
{
set_on_off(ch, &ch->comm, chan->bit,
FORMATF("%s channel is now OFF.{x", type),
FORMATF("%s channel is now ON.{x", type));
}
}
else
{
if (IS_SET(ch->comm, COMM_QUIET))
{
chprintln(ch, "You must turn off quiet mode first.");
return;
}
if (chan->bit > 0)
REMOVE_BIT(ch->comm, (chan->bit));
strcpy(arg_left, argument);
argument = one_argument(argument, command);
if (command[0] == '+')
{
CHAR_DATA *victim;
char buf[MIL + 200];
SOCIAL_DATA *soc;
char argx[MIL];
argument = one_argument(argument, command);
if (IS_NULLSTR(command))
{
chprintln
(ch, "{W+ <social> is used for channel based socials.{x");
return;
}
if (!(soc = find_social(command)))
{
chprintln(ch, "{WWhat kind of social is that?!?!{x");
return;
}
one_argument(argument, argx);
victim = NULL;
if (IS_NULLSTR(argx))
{
sprintf(buf, "%s %s{x", type, soc->char_no_arg);
act_new(buf, ch, NULL, NULL, TO_CHAR, POS_DEAD);
channel_social(ch, NULL, NULL, soc->others_no_arg, chan);
}
else if ((victim = get_char_world(ch, argx)) == NULL)
{
chprintln(ch, "They aren't here.");
return;
}
else
{
if (is_ignoring(victim, ch->name, IGNORE_SOCIALS))
{
act("$N is ignoring socials from you.", ch, NULL, victim,
TO_CHAR);
return;
}
if (!display_channel(ch, victim, chan, CHANNEL_SOCIAL, FALSE))
{
chprintln(ch, "They can't use that channel.");
return;
}
if (victim == ch)
{
sprintf(buf, "%s %s{x", type, soc->char_auto);
act_new(buf, ch, NULL, NULL, TO_CHAR, POS_DEAD);
channel_social(ch, victim, NULL, soc->others_auto, chan);
}
else
{
sprintf(buf, "%s %s{x", type, soc->char_found);
act_new(buf, ch, NULL, victim, TO_CHAR, POS_DEAD);
if (display_channel
(ch, victim, chan, CHANNEL_SOCIAL, FALSE))
{
sprintf(buf, "%s %s{x", type, soc->vict_found);
act_new(buf, ch, NULL, victim, TO_VICT, POS_DEAD);
}
channel_social(ch, victim, NULL, soc->others_found, chan);
}
}
return;
}
else if (command[0] == '!')
{
if (IS_NULLSTR(argument))
{
chprintln(ch, "Syntax: ! <argument>");
return;
}
chan_type = CHANNEL_EMOTE;
chprintlnf(ch, "%s %s %s{x", type,
IS_NPC(ch) ? ch->short_descr : ch->name, argument);
update_last_data(ch, ch, chan, argument, CHANNEL_EMOTE);
}
else if (command[0] == '@')
{
if (IS_NULLSTR(argument))
{
chprintln(ch, "Syntax: @ <argument>");
return;
}
chan_type = CHANNEL_THINK;
chprintlnf(ch, "%s %s . o O ( %s ){x", type,
IS_NPC(ch) ? ch->short_descr : ch->name, argument);
update_last_data(ch, ch, chan, argument, CHANNEL_THINK);
}
else if (is_exact_name(command, "wholist -who -w")
&& IS_NULLSTR(argument))
{
chan_type = CHANNEL_WHO;
chprintlnf(ch, "{WPlayers on %s{x", type);
chprintln(ch, "{C-------------------{x");
}
else if (is_name(command, "/last -hist") && IS_NULLSTR(argument))
{
if (IS_NPC(ch) || chan->page_length <= 0)
{
chprintln(ch, "Channel history unavailable.");
return;
}
chprintlnf(ch, "{WLast %d messages on %s{x", chan->page_length,
type);
chprintln(ch, "{C------------------------------{x");
view_last_data(ch, chan);
return;
}
else if (command[0] == '?' && IS_NULLSTR(argument))
{
chprintln(ch, "Syntax: <message> - send a message");
if (chan->bit > 0)
chprintln(ch,
" : - toggle channel on/off");
chprintln(ch,
" : -hist - display channel history");
chprintln(ch,
" : -who - display who is on channel");
chprintln(ch,
" : ! <emote> - send an emote over channel");
chprintln(ch,
" : + <social> [args] - do a social over channel");
chprintln(ch,
" : @ <message> - enclose a message in 'thought bubbles'");
chprintln(ch, " : ? - this message");
return;
}
else
{
chan_type = CHANNEL_NORMAL;
chprintlnf(ch, "%s You say '%s'{x", type, arg_left);
update_last_data(ch, ch, chan, arg_left, CHANNEL_NORMAL);
}
for (d = descriptor_first; d != NULL; d = d->next)
{
CHAR_DATA *victim;
if ((victim = d->character) == NULL)
continue;
if (victim == ch)
continue;
if (!display_channel(ch, victim, chan, chan_type, FALSE))
continue;
switch (chan_type)
{
default:
case CHANNEL_NORMAL:
if (d->connected == CON_PLAYING)
chprintlnf(victim, "%s %s says '%s'{x", type,
smash_colour(PERS(ch, victim)), arg_left);
update_last_data(ch, victim, chan, arg_left, CHANNEL_NORMAL);
break;
case CHANNEL_EMOTE:
case CHANNEL_THINK:
if (d->connected == CON_PLAYING)
{
if (chan_type == CHANNEL_THINK)
chprintlnf(victim, "%s %s . o O ( %s ){x", type,
smash_colour(PERS(ch, victim)), argument);
else
chprintlnf(victim, "%s %s %s{x", type,
smash_colour(PERS(ch, victim)), argument);
}
update_last_data(ch, victim, chan, argument, chan_type);
break;
case CHANNEL_WHO:
if (can_see(ch, victim))
chprintlnf(ch, "{W%s{x", PERS(victim, ch));
break;
}
}
}
}
CH_CMD(do_gossip)
{
public_ch(ch, argument, gcn_gossip);
}
CH_CMD(do_grats)
{
public_ch(ch, argument, gcn_grats);
}
CH_CMD(do_quote)
{
public_ch(ch, argument, gcn_quote);
}
CH_CMD(do_question)
{
public_ch(ch, argument, gcn_qa);
}
CH_CMD(do_answer)
{
public_ch(ch, argument, gcn_qa);
}
CH_CMD(do_music)
{
public_ch(ch, argument, gcn_music);
}
CH_CMD(do_ooc)
{
public_ch(ch, argument, gcn_ooc);
}
CH_CMD(do_immtalk)
{
public_ch(ch, argument, gcn_immtalk);
}
/* RT code to display channel status */
CH_CMD(do_channels)
{
int i;
/* lists all channels and their status */
chprintlnf(ch, " %-9s %-6s{w %s", "Command", "Status", "Description");
chprintln(ch, draw_line(ch, NULL, 0));
for (i = 0; i < maxChannel; i++)
{
if (!display_channel(ch, ch, &channel_table[i], CHANNEL_NORMAL, TRUE))
continue;
print_on_off(ch, !IS_SET(ch->comm, channel_table[i].bit),
channel_table[i].name, channel_table[i].description);
}
print_on_off(ch, IS_SET(ch->comm, COMM_SHOUTSOFF), "shouts",
"A global channel that transmits with a delay as if there is an echo.");
print_on_off(ch, IS_SET(ch->comm, COMM_DEAF), "deaf",
"Prevents you from hearing any tells.");
print_on_off(ch, IS_SET(ch->comm, COMM_QUIET), "quiet",
"Toggles whether you receive any channels at all.");
print_on_off(ch, IS_SET(ch->comm, COMM_AFK), "afk",
"Sets you Away From Keyboard.");
print_on_off(ch, IS_SET(ch->comm, COMM_NOGOCIAL), "nogocial",
"Toggles socials/emotes over public channels.");
chprintln(ch, draw_line(ch, NULL, 0));
if (IS_SET(ch->comm, COMM_SNOOP_PROOF))
chprintln(ch, "You are immune to snooping.");
if (ch->prompt != NULL)
{
chprintlnf(ch, "Your current prompt is: %s", ch->prompt);
}
if (IS_SET(ch->comm, COMM_NOSHOUT))
chprintln(ch, "You cannot shout.");
if (IS_SET(ch->comm, COMM_NOTELL))
chprintln(ch, "You cannot use tell.");
if (IS_SET(ch->comm, COMM_NOCHANNELS))
chprintln(ch, "You cannot use channels.");
if (IS_SET(ch->comm, COMM_NOEMOTE))
chprintln(ch, "You cannot show emotions.");
}
OLCED(chanedit_show)
{
CHANNEL_DATA *pChan;
EDIT_CHAN(ch, pChan);
chprintlnf(ch, "Index: %d (%s)", *pChan->index, gcn_name(pChan->index));
chprintlnf(ch, "Name: %s", pChan->name);
chprintlnf(ch, "Description: %s", pChan->description);
chprintlnf(ch, "Type: %s", flag_string(chan_types, pChan->spec_flag));
chprintlnf(ch, "Bit: %s", flag_string(comm_flags, pChan->bit));
chprintlnf(ch, "Hist Length: %d", pChan->page_length);
chprintlnf(ch, "Format: %s{x", pChan->format);
chprintlnf(ch, "Colour: %s(looks like this){x", get_chan_colour(pChan));
return TRUE;
}
OLCED(chanedit_create)
{
int j, i = maxChannel;
CHANNEL_DATA *pChan;
struct channel_type *new_table;
CHAR_DATA *pch;
maxChannel++;
alloc_mem(new_table, struct channel_type, maxChannel);
if (!new_table)
{
chprintln(ch, "Memory Allocation Failed!!! Unable to create channel.");
return FALSE;
}
for (j = 0; j < i; j++)
new_table[j] = channel_table[j];
free_mem(channel_table);
channel_table = new_table;
channel_table[i].index = &gcn_null;
channel_table[i].bit = 0;
channel_table[i].spec_flag = spec_none;
channel_table[i].page_length = 20;
channel_table[i].format = &str_empty[0];
channel_table[i].colour = &str_empty[0];
channel_table[i].name = str_dup(argument);
channel_table[i].description = &str_empty[0];
channel_table[i].custom_colour = -1;
for (pch = player_first; pch; pch = pch->next_player)
realloc_channel_history(pch->pcdata, -1, maxChannel, i);
pChan = &channel_table[i];
edit_start(ch, pChan, ED_CHAN);
chprintln(ch, "Channel created.");
return TRUE;
}
OLCED(chanedit_gcn)
{
CHANNEL_DATA *pChan;
int i;
EDIT_CHAN(ch, pChan);
if (IS_NULLSTR(argument))
{
chprintln(ch, "Syntax: gcn <gcn-name>");
return FALSE;
}
if (is_name(argument, "clear reset none"))
{
pChan->index = &gcn_null;
chprintln(ch, "GCN Entry cleared.");
return TRUE;
}
for (i = 0; gcn_table[i].name != NULL; i++)
{
if (!str_cmp(argument, gcn_table[i].name))
{
pChan->index = gcn_table[i].pgsn;
chprintlnf(ch, "Channel now uses global channel pointer '%s'.",
gcn_table[i].name);
return TRUE;
}
}
chprintln(ch, "That GCN hasn't been coded in yet.");
return FALSE;
}
OLCED(chanedit_colour)
{
CHANNEL_DATA *pChan;
int slot;
int cslot_lookup(const char *);
EDIT_CHAN(ch, pChan);
if (IS_NULLSTR(argument))
{
chprintln(ch, "Syntax: colour <colour code>");
chprintln(ch, " : colour <custom colour name>");
return FALSE;
}
if ((slot = cslot_lookup(argument)) != -1)
{
pChan->custom_colour = slot;
replace_string(pChan->colour, "");
}
else
{
replace_string(pChan->colour, argument);
pChan->custom_colour = -1;
}
chprintln(ch, "Channel colour changed.");
return TRUE;
}
OLCED(chanedit_delete)
{
CHANNEL_DATA *pChan;
EDIT_CHAN(ch, pChan);
if (str_cmp(argument, "confirm"))
{
chprintln
(ch,
"Typing 'delete confirm' will permanetely remove this channel!\n\r"
"-or- 'delete' with any argument will cancel the deletion.");
return FALSE;
}
else
{
int i, j = 0, c, old = maxChannel;
struct channel_type *new_table;
int channel_lookup(const char *);
CHAR_DATA *pch;
maxChannel--;
alloc_mem(new_table, struct channel_type, maxChannel);
if (!new_table)
{
chprintln(ch,
"Memory Allocation error!!! Unable to delete channel.");
return FALSE;
}
c = channel_lookup(pChan->name);
if (c == -1 && pChan->index != NULL)
c = *pChan->index;
for (i = 0; i < old; i++)
if (i != c)
new_table[j++] = channel_table[i];
free_mem(channel_table);
channel_table = new_table;
for (pch = player_first; pch != NULL; pch = pch->next_player)
realloc_channel_history(pch->pcdata, -1, maxChannel, old);
pChan = &channel_table[0];
ch->desc->pEdit = (void *) pChan;
chprintln(ch, "Channel deleted.");
}
return TRUE;
}
OLCED(chanedit_list)
{
int i;
for (i = 0; i < maxChannel; i++)
chprintlnf(ch, "%2d) %s{x", i, format_channel(&channel_table[i], ch));
return TRUE;
}