/
CVS/
boards/CVS/
clans/
gmc/CVS/
help/CVS/
include/CVS/
players/
progs/CVS/
races/CVS/
system/CVS/
text/
text/CVS/
todo/
todo/CVS/
units/CVS/
// $Id: Channels.pas,v 1.5 2001/06/14 18:19:41 druid Exp $

{
TODO:

Have channelignores save in pfile?
}

unit Channels;

interface

uses
  dtypes,
  chars,
  constants;

const
  channels_loaded : boolean = false;
  channelDataFile = 'system\channels.xml';

  CHANNEL_FLAG_LOG = BV00;
  CHANNEL_FLAG_HISTORY = BV01;
  CHANNEL_FLAG_ROOM = BV02;
  CHANNEL_FLAG_AREA = BV03;
  CHANNEL_FLAG_ALIGN = BV04;
  CHANNEL_FLAG_GLOBAL = BV05;
  CHANNEL_FLAG_CLAN = BV06;
  CHANNEL_FLAG_GROUP = BV07;

type
  GChannel =
    class
      channelname : string;
      command : string;
      alias : string;
      minleveluse : integer; // minimum level to *use* channel
      minlevelsee : integer; // minimum level to *see* channel
      comment : string;
      channelcolor : integer;
      verbyou : string;
      verbother : string;
{ <!-- Flags: 1: log this channel; 2: channel has history; 4: room; 8: area;
  16: align; 32: global/interalign; 64: clan; 128: group --> }
      channel_flags : cardinal;
      cost : integer;
      constructor Create(chname : string);
      function LOG() : boolean;
      function HISTORY() : boolean;
      function ROOM() : boolean;
      function AREA() : boolean;
      function ALIGN() : boolean;
      function GLOBAL() : boolean;
      function CLAN() : boolean;
      function GROUP() : boolean;
    end;

  EBooleanConvertError = class(GException);

  ChannelFieldEnum = (FieldNone,
                      FieldCommand,
                      FieldAlias,
                      FieldMinimumleveluse,
                      FieldMinimumlevelsee,
                      FieldComment,
                      FieldChannelcolor,
                      FieldVerbyou,
                      FieldVerbother,
                      FieldFlags,
                      FieldCost);

var
  channellist : GDLinkedList;

procedure load_channels();
procedure channelCommunicate(ch : GCharacter; param : string);
function lookupChannel(chname : string) : GChannel;
procedure to_channel(ch : GCharacter; arg : string; chanstr : string; color : integer); overload;
procedure to_channel(ch : GCharacter; arg : string; channel : GChannel; color : integer; localecho : boolean); overload;
procedure do_channel(ch : GCharacter; param : string);

implementation

uses
  SysUtils,
  mudsystem,
  mudthread,
  LibXmlParser,
  util,
  conns;

var
  errprefix : string;

constructor GChannel.Create(chname : string);
begin
  inherited Create();

  channelname := chname;
  command := '';
  alias := '';
  minleveluse := 1;
  minlevelsee := -1;
  comment := '';
  channelcolor := 15;
  verbyou := '%s';
  verbother := '%s';
  channel_flags := 0;
  cost := 0;
end;

function GChannel.LOG() : boolean;
begin
  Result := IS_SET(channel_flags, CHANNEL_FLAG_LOG);
end;

function GChannel.HISTORY() : boolean;
begin
  Result := IS_SET(channel_flags, CHANNEL_FLAG_HISTORY);
end;

function GChannel.ROOM() : boolean;
begin
  Result := IS_SET(channel_flags, CHANNEL_FLAG_ROOM);
end;

function GChannel.AREA() : boolean;
begin
  Result := IS_SET(channel_flags, CHANNEL_FLAG_AREA);
end;

function GChannel.ALIGN() : boolean;
begin
  Result := IS_SET(channel_flags, CHANNEL_FLAG_ALIGN);
end;

function GChannel.GLOBAL() : boolean;
begin
  Result := IS_SET(channel_flags, CHANNEL_FLAG_GLOBAL);
end;

function GChannel.CLAN() : boolean;
begin
  Result := IS_SET(channel_flags, CHANNEL_FLAG_CLAN);
end;

function GChannel.GROUP() : boolean;
begin
  Result := IS_SET(channel_flags, CHANNEL_FLAG_GROUP);
end;

function prep(str : string) : string;
begin
  Result := trim(uppercase(str));
end;

function StrToBoolean(str : string) : boolean;
begin
  if (prep(str) = 'TRUE') then
    Result := true
  else
  if (prep(str) = 'FALSE') then
    Result := false
  else
  if (prep(str) = 'YES') then
    Result := true
  else
  if (prep(str) = 'NO') then
    Result := false
  else
  if (prep(str) = 'ON') then
    Result := true
  else
  if (prep(str) = 'OFF') then
    Result := false
  else
  begin
    Result := false;
    raise EBooleanConvertError.CreateFmt('''%s'' invalid value for boolean; expected one of: true/false, yes/no, on/off', [str]);
  end;
end;

function BooleanToStr(b : boolean) : string;
begin
  if b then
    Result := 'true'
  else
    Result := 'false';
end;

function parseCardinal(str : string) : cardinal;
var
  i : integer;
  s : string;
begin
  Result := 0;
  repeat
    i := AnsiPos('|', str);
    s := copy(str, 1, i-1);
    if (s = '') then
      s := str
    else
      delete(str, 1, i);
    Result := Result + Cardinal(StrToInt(s)); // possible exceptions should be handled outside
  until (i = 0);
end;

function chan_ignored(ch : GPlayer; chanstr : string) : boolean;
var
  node : GListNode;
  tc : TChannel;
begin
  Result := false;
  
  node := ch.channels.head;
  while (node <> nil) do
  begin
    tc := node.element;
    if ((pos(prep(chanstr), prep(tc.channelname)) = 1)) then
    begin
      Result := tc.ignored;
      break;
    end;
    node := node.next;
  end;
end;

procedure channelAddHistory(vict, actor : GPlayer; channel : GChannel; str : string);
var
  node : GListNode;
  he : THistoryElement;
  tc : TChannel;
begin
  node := vict.channels.head;
  while (node <> nil) do
  begin
    tc := node.element;
    if (tc.channelname = channel.channelname) then
    begin
      he := THistoryElement.Create(vict.ansiColor(channel.channelcolor) + act_string(str, vict, nil, nil, actor));
      tc.history.insertLast(he);
      if (tc.history.getSize() > CHANNEL_HISTORY_MAX) then
      begin
        tc.history.remove(tc.history.head);
      end;
      break;
    end;
    node := node.next;
  end;
end;

procedure to_channel(ch : GCharacter; arg : string; chanstr : string; color : integer); overload;
var
  channel : GChannel;
begin
  channel := lookupChannel(chanstr);
  to_channel(ch, arg, channel, color, true);
end;

procedure to_channel(ch : GCharacter; arg : string; channel : GChannel; color : integer; localecho : boolean); overload;
var
   vict : GCharacter;
   node, node_next : GListNode;
begin
  if (not channels_loaded) then
  begin
    write_console('to_channel(): channels not loaded! Perhaps error loading/parsing ' + channelDataFile + '? Please correct this error.');
    exit;
  end;
  
  if (channel = nil) then
  begin
    bugreport('to_channel', 'Channels.pas', 'channel = nil',
              'Procedure was passed a nil argument.');
    exit;
  end;
    
  if localecho and (ch <> nil) then // this is for clannotifies/groupnotifies
    act(color, arg, false, ch, nil, nil, TO_CHAR);

  node_next := char_list.head;
  while (node_next <> nil) do
  begin
    node := node_next;
    node_next := node_next.next;

    vict := node.element;

{    if (channel <> CHANNEL_AUCTION) and (channel <> CHANNEL_CLAN) and (vict=ch) then continue;
    if (channel = CHANNEL_CHAT) and (not ch.IS_SAME_ALIGN(vict)) then continue;
    if (channel = CHANNEL_BABBEL) and (not ch.IS_SAME_ALIGN(vict)) then continue;
    if (channel = CHANNEL_RAID) and ((vict.level < 100) or (not ch.IS_SAME_ALIGN(vict))) then continue;
    if (channel = CHANNEL_AUCTION) and (not ch.IS_SAME_ALIGN(vict)) then continue;
    if (channel = CHANNEL_IMMTALK) and (not vict.IS_IMMORT) then continue;
    if (channel = CHANNEL_CLAN) and (vict.clan<>ch.clan) then continue;
    if (channel = CHANNEL_YELL) and (vict.room.area <> ch.room.area) then continue;
    if (channel = CHANNEL_LOG) and (not vict.IS_NPC) and (vict.level<system_info.level_log) then continue;}

    with channel do
    begin
      if (ch <> nil) then
      begin
        if (vict = ch) then continue;
        if (vict.level < minlevelsee) then continue;
        if (ROOM() and (vict.room <> ch.room)) then continue;
        if (AREA() and (vict.room.area <> ch.room.area)) then continue;
        if (ALIGN() or not GLOBAL()) and (not ch.IS_SAME_ALIGN(vict)) then continue;
        if (CLAN() and (vict.clan <> ch.clan)) then continue;
        if (GROUP() and (vict.leader <> ch.leader)) then continue;
      end
      else // ch = nil
      begin
        if (not vict.IS_NPC()) and (vict.level < minlevelsee) then continue;
      end;
    end;

    if (vict <> nil) and (not vict.IS_NPC()) and channel.HISTORY() then
    begin
      channelAddHistory(GPlayer(vict), GPlayer(ch), channel, arg);
    end;

    if (vict <> nil) and (not vict.IS_NPC()) then
    begin
      if (not chan_ignored(GPlayer(vict), channel.channelname)) or (ch.IS_IMMORT()) then
        act(color, arg, false, vict, nil, ch, TO_CHAR)
    end
    else
      act(color, arg, false, vict, nil, ch, TO_CHAR);
  end;
end;

function lookupChannel(chname : string) : GChannel;
var
  node : GListNode;
  chan : GChannel;
begin
  Result := nil;
  node := channellist.head;
  while (node <> nil) do
  begin
    chan := node.element;
    if (chname = chan.channelname) or
       (pos(chname, chan.command) = 1) or
       ((chan.alias <> '') and (copy(chname, 1, length(chan.alias)) = chan.alias)) then
    begin
      Result := chan;
      exit;
    end;
    node := node.next;
  end;
end;

procedure channelCommunicate(ch : GCharacter; param : string);
var
  arg0, buf : string;
  chan : GChannel;
  node, node2 : GListNode;
  tc : TChannel;
  he : THistoryElement;
begin
  if (not channels_loaded) then
  begin
    write_console('channelCommunicate(): channels not loaded! Perhaps error loading/parsing ' + channelDataFile + '? Please correct this error.');
    exit;
  end;
  param := one_argument(param, arg0);
  chan := lookupChannel(arg0);
  if (ch = nil) then
  begin
    bugreport('channelCommunicate', 'Channels.pas', 'ch = nil',
              'channelCommunicate() was passed nil for variable ch.');
    exit;
  end;
  if (chan = nil) then
  begin
    bugreport('channelCommunicate', 'Channels.pas', 'chan = nil',
              'Could not find a GChannel record for given parameter.');
    exit;
  end;

  if (chan.CLAN() and (ch.clan = nil)) then
    begin
    ch.sendBuffer('But you aren''t in a clan!'#13#10);
    exit;
    end;
  
  if ((length(param) = 0) and chan.HISTORY()) then
  begin
    if (ch.IS_NPC()) then
    begin
      ch.sendBuffer('Channel histories not available for NPCs.'#13#10);
      exit;
    end;
    if chan.HISTORY() then
    begin
      node := GPlayer(ch).channels.head;
      while (node <> nil) do
      begin
        tc := node.element;
        if (tc.channelname = chan.channelname) then
        begin
          node2 := tc.history.head;
          while (node2 <> nil) do
          begin
            he := node2.element;
            ch.sendBuffer(he.contents^ + #13#10);
            node2 := node2.next;
          end;
          break;
        end;
        node := node.next;
      end;
    end;
  end
  else
  begin
    if (chan.LOG()) then
    begin
      write_console(Format('Logged channel [%s]: %s ' + chan.verbother, [chan.channelname, ch.name^, param]));
    end;
    
    if (not ch.IS_IMMORT()) then
      ch.mv := ch.mv - chan.cost;
    
    buf := Format('You ' + chan.verbyou, [param]);
    act(chan.channelcolor, buf, false, ch, nil, nil, TO_CHAR);

    if (not ch.IS_NPC() and chan.HISTORY()) then // add text to own history
      channelAddHistory(GPlayer(ch), nil, chan, buf);

    buf := Format('$N ' + chan.verbother, [param]);
    to_channel(ch, buf, chan, chan.channelcolor, false);
  end;
end;

procedure processChannels(parser : TXmlParser; errprefix : string; Field : ChannelFieldEnum; ptr : pointer);
var
  attr : TNvpList;
  i : integer;
  chan : GChannel;
  chanparam : GChannel;
  str : string;
begin
  chanparam := GChannel(ptr);

  while (parser.Scan()) do
    case parser.CurPartType of // Here the parser tells you what it has found
{      ptDtdc:
        begin
          writeln('ptDtdc: ' + StrSFPas (Parser.CurStart, Parser.CurFinal));
        end;}
{      ptEmptyTag:
        begin
          writeln('ptEmptyTag');
        end;}
{      ptCData    : // Process Parser.CurContent field here
        begin
          writeln('ptCData: CurContent: ' + parser.CurContent);
        end;}
{        ptPI       : // Process PI here (Parser.CurName is the target, Parser.CurContent)
        begin
          writeln('ptPI: CurName: ' + parser.CurName + ' CurContent: ' + parser.CurContent);
        end;}
      ptStartTag: // Process Parser.CurName and Parser.CurAttr (see below) fields here
        begin
          if (prep(parser.CurName) = 'CHANNELS') then
            // this is ok
          else
          if (prep(parser.CurName) = 'CHANNELDATA') then
          begin
            if (parser.CurAttr.Count > 0) then
            begin
              attr := Parser.CurAttr;
              str := '';
              for i := 0 to (attr.Count - 1) do
              begin
                if (prep(TNvpNode(attr[i]).Name) = 'NAME') then
                  str := TNvpNode(attr[i]).Value;
              end;
              if (str = '') then
              begin
                write_console(errprefix + 'found channeldata tag with fields but no name field (error in channelfile).');
              end
              else
              begin
                chan := GChannel.Create(uppercase(str));
                processChannels(parser, errprefix, FieldNone, chan);
              end;
            end
            else
            begin
              write_console(errprefix + 'found channeldata tag without a name field (error in channelfile).');
            end;
          end
          else
          if (prep(parser.CurName) = 'COMMAND') then
            processChannels(parser, errprefix, FieldCommand, ptr)
          else
          if (prep(parser.CurName) = 'ALIAS') then
            processChannels(parser, errprefix, FieldAlias, ptr)
          else
          if (prep(parser.CurName) = 'MINIMUMLEVELUSE') then
            processChannels(parser, errprefix, FieldMinimumleveluse, ptr)
          else
          if (prep(parser.CurName) = 'MINIMUMLEVELSEE') then
            processChannels(parser, errprefix, FieldMinimumlevelsee, ptr)
          else
          if (prep(parser.CurName) = 'COMMENT') then
            processChannels(parser, errprefix, FieldComment, ptr)
          else
          if (prep(parser.CurName) = 'CHANNELCOLOR') then
            processChannels(parser, errprefix, FieldChannelcolor, ptr)
          else
          if (prep(parser.CurName) = 'VERBYOU') then
            processChannels(parser, errprefix, FieldVerbyou, ptr)
          else
          if (prep(parser.CurName) = 'VERBOTHER') then
            processChannels(parser, errprefix, FieldVerbother, ptr)
          else
          if (prep(parser.CurName) = 'FLAGS') then
            processChannels(parser, errprefix, FieldFlags, ptr)
          else
          if (prep(parser.CurName) = 'COST') then
            processChannels(parser, errprefix, FieldCost, ptr);
          // if unknown tag found, silently ignore, it'll be handled in ptContent (FieldNone)
        end;
      ptContent:
        begin
          if (chanparam = nil) then
            write_console(errprefix + '(ptContent) chanparam = nil (error in code).')
          else
            case Field of
              FieldNone:
                begin
                  write_console(errprefix + 'found unrecognized tag ''' + parser.CurName + ''' with content ''' + parser.CurContent + ''' (error in channelfile).');
                end;
              FieldCommand:
                begin
                  chanparam.command := uppercase(parser.CurContent);
                  exit;
                end;
              FieldAlias:
                begin
                  chanparam.alias := uppercase(parser.CurContent);
                  exit;
                end;
              FieldMinimumleveluse:
                begin
                  try
                    chanparam.minleveluse := StrToInt(parser.CurContent);
                    if ((chanparam.minleveluse < LEVEL_START) or (chanparam.minleveluse > LEVEL_MAX_IMMORTAL)) then
                    begin
                      write_console(errprefix + Format('found invalid value for Minimumleveluse tag (%d), value supposed to be >= %d and =< %d.', [chanparam.minleveluse, LEVEL_START, LEVEL_MAX_IMMORTAL]));
                    end;
                  except
                    on EConvertError do
                    begin
                      write_console(errprefix + Format('found invalid value for Minimumleveluse tag (''%s''), setting to %d.', [parser.CurContent, LEVEL_MAX_IMMORTAL]));
                      chanparam.minleveluse := LEVEL_MAX_IMMORTAL;
                    end;
                  end;
                  exit;
                end;
              FieldMinimumlevelsee:
                begin
                  try
                    chanparam.minlevelsee := StrToInt(parser.CurContent);
                    if ((chanparam.minlevelsee < LEVEL_START) or (chanparam.minlevelsee > LEVEL_MAX_IMMORTAL)) then
                    begin
                      write_console(errprefix + Format('found invalid value for Minimumlevelsee tag (%d), value supposed to be >= %d and =< %d.', [chanparam.minlevelsee, LEVEL_START, LEVEL_MAX_IMMORTAL]));
                    end;
                  except
                    on EConvertError do
                    begin
                      write_console(errprefix + Format('found invalid value for Minimumlevelsee tag (''%s''), setting to %d.', [parser.CurContent, LEVEL_MAX_IMMORTAL]));
                      chanparam.minlevelsee := LEVEL_MAX_IMMORTAL;
                    end;
                  end;
                  exit;
                end;
              FieldComment:
                begin
                  chanparam.comment := parser.CurContent;
                  exit;
                end;
              FieldChannelcolor:
                begin
                  try
                    chanparam.channelcolor := StrToInt(parser.CurContent);
                    if (chanparam.channelcolor < 0) then
                    begin
                      write_console(errprefix + Format('found invalid value for Channelcolor tag (%d), value supposed to be >= %d.', [chanparam.channelcolor, 0]));
                    end;
                  except
                    on EConvertError do
                    begin
                      write_console(errprefix + Format('found invalid value for Channelcolor tag (''%s''), setting to %d.', [parser.CurContent, LEVEL_MAX_IMMORTAL]));
                      chanparam.channelcolor := LEVEL_MAX_IMMORTAL;
                    end;
                  end;
                  exit;
                end;
              FieldVerbyou:
                begin
                  chanparam.verbyou := parser.CurContent;
                  exit;
                end;
              FieldVerbother:
                begin
                  chanparam.verbother := parser.CurContent;
                  exit;
                end;
              FieldFlags:
                begin
                  try
                    chanparam.channel_flags := parseCardinal(parser.CurContent);
                  except
                    on E: EConvertError do
                    begin
                      write_console(errprefix + Format('error parsing value for Flags tag (''%s''): %s.', [parser.CurContent, e.Message]));
                    end;
                  end;
                  exit;
                end;
              FieldCost:
                begin
                  try
                    chanparam.cost := StrToInt(parser.CurContent);
                  except
                    on EConvertError do
                    begin
                      write_console(errprefix + Format('found invalid value for Cost tag (''%s''), setting to %d.', [parser.CurContent, 0]));
                      chanparam.cost := 0;
                    end;
                  end;
                  exit;
                end;
            else
              write_console(errprefix + '(ptContent) found unrecognized Field enum (possible error in code).');
            end;
        end;
      ptEndTag   : // Process End-Tag here (Parser.CurName)
        begin
          if (prep(parser.CurName) = 'CHANNELS') then
            // this is ok
          else
          if (prep(parser.CurName) = 'CHANNELDATA') then
          begin
            channellist.insertLast(chanparam);
            exit;
          end
          else
          begin
//            write_console(errprefix + 'found unrecognized EndTag ''' + parser.CurName + ''' (error in channelfile).');
          end;
        end;
    end;
end;

procedure registerChannels(list : GDLinkedList);
var
  node : GListNode;
  chan : GChannel;
  cmd : GCommand;
  alias : GCommand;
begin
  node := list.head;
  while (node <> nil) do
  begin
    chan := node.element;
    
    if (chan.command <> '') then
    begin
      cmd := GCommand.Create();
      cmd.position := POS_SLEEPING;
      cmd.name := chan.command;
      cmd.func_name := 'channelCommunicate';
      cmd.level := chan.minleveluse;
      cmd.ptr := @channelCommunicate;
      cmd.addArg0 := true;

      commands.put(cmd.name, cmd);

      if (chan.alias <> '') then
      begin
        alias := GCommand.Create();
        alias.position := cmd.position;
        alias.name := chan.alias;
        alias.func_name := cmd.func_name;
        alias.level := chan.minleveluse;
        alias.ptr := cmd.ptr;
        alias.addArg0 := cmd.addArg0;

        commands.put(alias.name, alias);
      end;
    end;
    
    node := node.next;
  end;
end;

procedure writeChannelsToConsole();
var
  node : GListNode;
  chan : GChannel;
begin
  node := channellist.head;
  while (node <> nil) do
  begin
    chan := node.element;

    with chan do
    begin
      writeln('channelname: ' + channelname);
      writeln('  command:       ' + command);
      writeln('  alias:         ' + alias);
      writeln('  minleveluse:   ' + IntToStr(minleveluse));
      writeln('  minlevelsee:   ' + IntToStr(minlevelsee));
      writeln('  comment:       ' + comment);
      writeln('  channelcolor:  ' + IntToStr(channelcolor));
      writeln('  verbyou:       ' + verbyou);
      writeln('  verbother:     ' + verbother);
      writeln('  flags:         ' + IntToStr(integer(channel_flags)));
    end;
    writeln;

    node := node.next;
  end;
end;

procedure load_channels();
var
  parser : TXmlParser;
  node : GListNode;
  chan : GChannel;
begin
  parser := TXmlParser.Create();
  parser.Normalize := true;
  parser.LoadFromFile(channelDataFile);
  
  if (parser.Source <> channelDataFile) then
  begin
    write_console('Could not open ' + channelDataFile + ', channels disabled.');
    exit;
  end;

  errprefix := 'Error processing ' + channelDataFile + ': ';
  
  parser.StartScan();
  processChannels(parser, errprefix, FieldNone, nil);
  parser.Free();

  node := channellist.head;
  while (node <> nil) do // cpl sanity checks
  begin
    chan := node.element;
    with chan do
    begin
      if (verbyou = '') then
        verbyou := command;
      if (verbother = '') then
        verbother := verbyou + 's';
      if (minlevelsee = -1) then
        minlevelsee := minleveluse;
    end;
    node := node.next;
  end;

  chan := lookupChannel(CHANNEL_LOG);
  if (chan = nil) then
  begin
    write_console('PANIC: no LOG channel found while loading channels from ' + channelDatafile + '.');
    write_console('PANIC: this channel is *ESSENTIAL* to this mud.');
    write_console('PANIC: Please add the following to ' + channelDataFile + ':');
    write_console('         <ChannelData Name="log">');
    write_console('           <Flags>1|32</Flags>');
    write_console('         </ChannelData>');
    halt;
  end
  else
  begin
    if (chan.minlevelsee <> system_info.level_log) then
    begin
      write_console(Format('Warning: value of minimumlevelsee field of channel ''log'' (%d) doesn''t equal LevelLog value in system\sysdata.dat ().', [chan.minlevelsee, system_info.level_log]));
      write_console('Warning: setting minimumlevelsee to value in system\sysdata.dat.');
      chan.minlevelsee := system_info.level_log;
    end;
  end;

  registerChannels(channellist);

  if (channellist.getSize() < 1) then
    write_console('no channels loaded from ' + channelDataFile + ', please check that file. Channels disabled for now.')
  else
    write_console(Format('%d channels loaded from file %s and registered.', [channellist.getSize(), channelDataFile]));

//  writeChannelsToConsole();
  channels_loaded := true;
end;

procedure do_channel(ch : GCharacter; param : string);
var
  node : GListNode;
  chan : GChannel;
  buf : string;
  arg1, arg2 : string;
  tc : TChannel;
begin
  param := one_argument(param, arg1);
  param := one_argument(param, arg2);
  if (length(arg1) = 0) then
  begin
    ch.sendBuffer('Usage: CHANNEL <list>|[<channelname> <on/off>] '#13#10);
    ch.sendBuffer('Note: on/off feature not implemented yet.'#13#10);
    exit;
  end
  else
  if (prep(arg1) = 'LIST') then
  begin
    node := channellist.head;
    while (node <> nil) do
    begin
      chan := node.element;
      if (ch.level >= chan.minleveluse) then
      begin
        buf := '';

        if (not ch.IS_NPC()) then
          if (chan_ignored(GPlayer(ch), chan.channelname)) then
            buf := '$B$7ignored$A$7'
          else
            buf := '$B$7not ignored$A$7';

        with chan do
        begin
          if (ch.IS_IMMORT()) then
          begin
            buf := Format('%s%s$A$7: (%s) command: %s; alias: %s; minleveluse: %d; minlevelsee: %d.$A$7'#13#10, [ch.ansiColor(channelcolor), channelname, buf, command, alias, minleveluse, minlevelsee]);
            buf := buf + '  verbyou: "' + verbyou + '" verbother: "' + verbother + '"'#13#10;
          end
          else
          begin
            buf := Format('%s%s$A$7: (%s) command: %s; alias: %s; minimumlevel: %d.$A$7'#13#10, [ch.ansiColor(channelcolor), channelname, buf, command, alias, minleveluse]);
          end;
          buf := buf + Format('  %s'#13#10, [comment]);
        end;
        ch.sendPager(act_string(buf, ch, nil, nil, nil));
      end;
      node := node.next;
    end;
  end
  else
  begin
    if (ch.IS_NPC()) then
    begin
      ch.sendBuffer('This command is not available for NPCs.'#13#10);
      exit;
    end;

    node := GPlayer(ch).channels.head;
    while (node <> nil) do
    begin
      tc := node.element;
      if ((pos(prep(arg1), prep(tc.channelname)) = 1)) then
      begin
        if (length(arg2) = 0) then
        begin
          if (tc.ignored) then
            buf := '$B$7ignored$A$7'
          else
            buf := '$B$7not ignored$A$7';
          ch.sendBuffer(act_string(Format('%s is currently %s.'#13#10, [tc.channelname, buf]), ch, nil, nil, nil));
        end
        else
        begin
          try
            tc.ignored := not StrToBoolean(arg2);
          except
            on E: EBooleanConvertError do
            begin
              ch.sendBuffer(Format('Invalid argument ''%s''.'#13#10, [arg2]));
            end;
          end;
          
          if (tc.ignored) then
            buf := '$B$7ignored$A$7'
          else
            buf := '$B$7not ignored$A$7';
          ch.sendBuffer(act_string(Format('%s is now %s.'#13#10, [tc.channelname, buf]), ch, nil, nil, nil));
        end;
      end;
      node := node.next;
    end;
  end;
end;

begin
  channellist := GDLinkedList.Create();
end.