grendel-1.0.0a7/backup/
grendel-1.0.0a7/bin/
grendel-1.0.0a7/boards/
grendel-1.0.0a7/clans/
grendel-1.0.0a7/documentation/todo/
grendel-1.0.0a7/help/
grendel-1.0.0a7/logs/
grendel-1.0.0a7/players/
grendel-1.0.0a7/progs/
grendel-1.0.0a7/races/
grendel-1.0.0a7/src/contrib/
grendel-1.0.0a7/src/modules/speller/
grendel-1.0.0a7/src/modules/status/
grendel-1.0.0a7/src/tests/
grendel-1.0.0a7/src/tests/dunit/
{
  Summary:
    Command interpreter and supporting code
  
  ##  $Id: commands.pas,v 1.14 2004/04/10 22:24:03 druid Exp $
}

unit commands;

interface

uses
    Classes,
{$IFDEF WIN32}
    Windows,
{$ENDIF}
{$IFDEF LINUX}
    Libc,
{$ENDIF}
    SysUtils,
    Math,
    constants,
    chars,
    dtypes;
    

type
  COMMAND_FUNC = procedure(ch : GCharacter; param : string);

  GCommandFunc = class
  private
		_name : string;
		_func : COMMAND_FUNC;
		
	public
		property name : string read _name write _name;
		property func : COMMAND_FUNC read _func write _func;
  end;

  GCommand = class
  private
    _name : string;
    _level : integer;             { minimum level }
  
  public
    func_name : string;
    ptr : COMMAND_FUNC;
    allowed_states : set of STATE_IDLE .. STATE_SLEEPING;      { allowed states }
    addArg0 : boolean;           { send arg[0] (the command itself) to func? }

    property name : string read _name write _name;
    property level : integer read _level write _level;
  end;

var
   funcList, commandList : GHashTable;

procedure interpret(ch : GCharacter; line : string);

procedure registerCommand(const name : string; func : COMMAND_FUNC);
procedure unregisterCommand(const name : string);

procedure initCommands();
procedure loadCommands();
procedure cleanupCommands();

implementation

uses
    conns,
    fsys,
    strip,
    util,
    mudsystem,
    console,
    md5,
    timers,
    player;
    

procedure do_dummy(ch : GCharacter; param : string);
begin
  ch.sendBuffer('This is a DUMMY command, and doesn''t perform any action.'#13#10);
  ch.sendBuffer('Either this command has not been implemented yet,'#13#10);
  ch.sendBuffer('or the server is reconfiguring itself with new code.'#13#10);
  ch.sendBuffer('Please contact the administration if this persists for more than an hour.'#13#10);
end;

function findCommand(const s : string) : COMMAND_FUNC;
var
   f : GCommandFunc;
begin
  f := GCommandFunc(funcList.get(s));
  
  if (f = nil) then
    begin
    writeConsole('Could not find function for command "' + s + '"');
    Result := @do_dummy;
    end
  else   
    Result := f.func;
end;

// Load the commands
procedure loadCommands();
var 
  af : GFileReader;
  param, s, g : string;
  cmd : GCommand;
  alias : GCommand;
begin
  try
    af := GFileReader.Create(SystemDir + 'commands.dat');
  except
    exit;
  end;

  repeat
    repeat
      s := af.readLine();
    until (uppercase(s) = '#COMMAND') or (af.eof());

    if (af.eof()) then
      break;

    alias := nil;
    cmd := GCommand.Create();
    cmd.allowed_states := [];

    with cmd do
      repeat
      s := af.readLine();
      g := uppercase(left(s,':'));
      
      param := trim(right(s, ':'));

      if (g = 'NAME') then
        name := uppercase(param)
      else
      if (g = 'ALIAS') then
        begin
        // create an alias
        alias := GCommand.Create();
        alias.name := uppercase(param);
        end
      else
      if (g = 'LEVEL') then
        level := strtoint(param)
      else
      if (g = 'POSITION') then
      	begin
      	writeConsole('deprecated element position at line ' + IntToStr(af.line));
      	end
      else
      if (g = 'ALLOWED_STATES') then
        begin
        while (pos(',', param) > 0) do
        	begin
        	s := uppercase(left(param, ','));
        	
        	if (s = 'IDLE') then
        		cmd.allowed_states := cmd.allowed_states + [STATE_IDLE]
        	else
        	if (s = 'FIGHTING') then
        		cmd.allowed_states := cmd.allowed_states + [STATE_FIGHTING]
        	else
        	if (s = 'RESTING') then
        		cmd.allowed_states := cmd.allowed_states + [STATE_RESTING]
        	else
        	if (s = 'MEDITATING') then
        		cmd.allowed_states := cmd.allowed_states + [STATE_MEDITATING]
        	else
        	if (s = 'SLEEPING') then
        		cmd.allowed_states := cmd.allowed_states + [STATE_SLEEPING];
        	
        	param := right(param, ',');
        	end;
        
				s := uppercase(left(param, ','));

				if (s = 'IDLE') then
					cmd.allowed_states := cmd.allowed_states + [STATE_IDLE]
				else
				if (s = 'FIGHTING') then
					cmd.allowed_states := cmd.allowed_states + [STATE_FIGHTING]
				else
				if (s = 'RESTING') then
					cmd.allowed_states := cmd.allowed_states + [STATE_RESTING]
				else
				if (s = 'MEDITATING') then
					cmd.allowed_states := cmd.allowed_states + [STATE_MEDITATING]
				else
				if (s = 'SLEEPING') then
					cmd.allowed_states := cmd.allowed_states + [STATE_SLEEPING];
        end
      else
      if (g = 'FUNCTION') then
        begin
        func_name := right(s,' ');
        ptr := findCommand(func_name);
        end
      else
      if (g = 'ADDARG0') then
        begin
          addarg0 := (trim(uppercase(right(s,' '))) = 'TRUE');
        end;
      until (uppercase(s)='#END') or (af.eof());
      
		if (cmd.allowed_states = []) then
		 	cmd.allowed_states := [STATE_MEDITATING, STATE_IDLE, STATE_RESTING, STATE_FIGHTING];
		 	
    if (assigned(cmd.ptr)) then
      begin
      commandList.put(cmd.name, cmd);

      if (alias <> nil) then
        begin
        // update settings
        alias.level := cmd.level;
        alias.ptr := cmd.ptr;
        alias.func_name := cmd.func_name;
        alias.allowed_states := cmd.allowed_states;
        alias.addarg0 := cmd.addarg0;

        commandList.put(alias.name, alias);
        end;
      end
    else
      begin
      cmd.Free();

      if (alias <> nil) then
        alias.Free();
      end;
  until (af.eof());

  af.Free();
end;

// Strip '$' from commandline
procedure clean_cmdline(var line : string);
var
   d : integer;
begin
  d := pos('$', line);

  while (d > 0) do
    begin
    delete(line, d, 1);

    d := pos('$', line);
    end;
end;

// Interpret the command
procedure interpret(ch : GCharacter; line : string);
var
  a : longint;
  gc : GCommand;
  cmd : GCommand;
  cmdline, param : string;
  hash : cardinal;
  iterator : GIterator;
  timer : GTimer;
begin
  if (not ch.IS_NPC) and (GPlayer(ch).switching <> nil) then
    begin
    interpret(GPlayer(ch).switching, line);
    exit;
    end;

  { Check if keyboard is locked - Nemesis }
  if (not ch.IS_NPC) then
    begin
    if (GPlayer(ch).conn <> nil) and (ch.IS_KEYLOCKED) then
      begin
      if (length(line) = 0) then
        begin
        ch.sendBuffer('Enter your password to unlock keyboard.'#13#10);
        exit;
        end;

      if (not MD5Match(GPlayer(ch).md5_password, MD5String(line))) then
        begin
        ch.sendBuffer('Wrong password!'#13#10);
        exit;
        end
      else
        begin
        GPlayer(ch).afk := false;
        GPlayer(ch).keylock := false;

        act(AT_REPORT,'You are now back at your keyboard.',false,ch,nil,nil,to_char);
        act(AT_REPORT,'$n has returned to $s keyboard.',false,ch,nil,nil,to_room);
        exit;
        end;
      end;

    { AFK revised with keylock - Nemesis }
    if (GPlayer(ch).conn <> nil) and (ch.IS_AFK) and (not ch.IS_KEYLOCKED) then
      begin
      GPlayer(ch).afk := false;

      act(AT_REPORT,'You are now back at your keyboard.',false,ch,nil,nil,to_char);
      act(AT_REPORT,'$n has returned to $s keyboard.',false,ch,nil,nil,to_room);
      end;
    end;

  timer := hasTimer(ch, TIMER_ACTION);

  if (timer <> nil) then
    begin
    act(AT_REPORT, 'You stop your ' + timer.name + '.', false, ch, nil, nil, TO_CHAR);
    unregisterTimer(ch, TIMER_ACTION);
    end;

  if (length(line) = 0) then
    begin
    ch.sendBuffer(' ');
    exit;
    end;

  // Char is being snooped
  if (ch.snooped_by <> nil) then
    GPlayer(ch.snooped_by).conn.send(line + #13#10);

  clean_cmdline(line);

  param := one_argument(line, cmdline);
  cmdline := uppercase(cmdline);

  cmd := nil;

  hash := commandList.getHash(cmdline);
  
  // try to find exact match first 
  iterator := commandList.buckets[hash].iterator();
  
  while (iterator.hasNext()) do
  	begin
    gc := GCommand(GHashValue(iterator.next()).value);
    
    if (cmdline = gc.name) then
    	begin
    	cmd := gc;
    	break;
    	end;
  	end;
  	
  iterator.Free();

	// if an exact match does not exist, try to find a substring
	if (cmd = nil) then
		begin
  	iterator := commandList.buckets[hash].iterator();
  
  	while (iterator.hasNext()) do
  		begin
  	  gc := GCommand(GHashValue(iterator.next()).value);

	    if ((pos(cmdline, gc.name) = 1) and (length(cmdline) <= length(gc.name)) and (length(cmdline) > 1)) or
       ((copy(cmdline, 1, length(gc.name)) = gc.name) and (length(cmdline) = 1))
       then
      	begin
      	cmd := gc;
      	break;
      	end;
      end;

		iterator.Free();
		end;

  if (cmd <> nil) then
    begin
    a := ch.getTrust();
    
    if (a >= cmd.level) then
      begin
      if not (ch.state in cmd.allowed_states) then
        case ch.state of
            STATE_SLEEPING: ch.sendBuffer('You are off to dreamland.'#13#10);
          STATE_MEDITATING: ch.sendBuffer('You must break out of your trance first.'#13#10);
             STATE_RESTING: ch.sendBuffer('You are resting.'#13#10);
            STATE_FIGHTING: ch.sendBuffer('You are fighting!'#13#10);
            		STATE_IDLE: ch.sendBuffer('You can not do that now.'#13#10);
          else
            writeConsole('Illegal state ' + IntToStr(ch.state) + '!');
        end
      else
        begin
        try
          if (system_info.log_all) or (ch.logging) then
            writeConsole(ch.name + ': ' + line);
          if (cmd.level >= LEVEL_IMMORTAL) and (not IS_SET(GPlayer(ch).flags, PLR_CLOAK)) then
            writeConsole(ch.name + ': ' + cmd.name + ' (' + inttostr(cmd.level) + ')');

          if (cmd.addarg0) then
            cmd.ptr(ch, cmdline + ' ' + param)
          else
            cmd.ptr(ch, param);

          ch.last_cmd := @cmd.ptr;
        except
        end;
        end;
      end
    else
      cmd := nil;
    end;

  if (cmd = nil) and (not checkSocial(ch, cmdline, param)) then
    begin
    a := random(9);
    if a<1 then
      cmdline := 'Sorry, that command doesn''t exist in my vocabulaire!'
    else
    if a<2 then
      cmdline := 'I don''t understand you.'
    else
    if a<3 then
      cmdline := 'What are you saying?'
    else
    if a<4 then
      cmdline := 'Learn some english!'
    else
    if a<5 then
      cmdline := 'Hey, I don''t know that command. Try again.'
    else
    if a<6 then
      cmdline := 'What??'
    else
    if a<7 then
      cmdline := 'Huh?'
    else
    if a<8 then
      cmdline := 'Yeah, right!'
    else
    if a<9 then
      cmdline := 'What you say??';

    act(AT_DGREEN, cmdline, false, ch, nil, nil, TO_CHAR);
    end;
end;

// command stuff
procedure registerCommand(const name : string; func : COMMAND_FUNC);
var
   g : GCommandFunc;
   c : GCommand;
   iterator : GIterator;
begin
  g := GCommandFunc(funcList.get(name));
  
  if (g <> nil) then
    begin
    bugreport('registerCommand', 'mudthread.pas', 'Command ' + name + ' registered twice.');
    exit;
    end;

  g := GCommandFunc.Create();

  g.name := name;
  g.func := func;

  funcList.put(name, g);
  
  iterator := commandList.iterator();
  
  while (iterator.hasNext()) do
    begin
    c := GCommand(iterator.next());
    
    if (c.func_name = name) then
      begin
//      writeConsole('Found empty command with my name: ' + c.name);
      c.ptr := func;
      end;
    end;  
   
  iterator.Free();
end;

procedure unregisterCommand(const name : string);
var
  g : GCommandFunc;
  c : GCommand;
  iterator : GIterator;
begin
  g := GCommandFunc(funcList.get(name));
  
  if (g = nil) then
    begin
    bugreport('unregisterCommand', 'mudthread.pas', 'Command ' + name + ' not registered');
    exit;
    end
  else
    begin
    iterator := commandList.iterator();
    
    while (iterator.hasNext()) do
      begin
      c := GCommand(iterator.next());
      
      if (@c.ptr = @g.func) then
        begin
//        writeConsole('Resetting command with my name: ' + c.name);
        c.ptr := do_dummy;
        end;
      end;
    
    funcList.remove(name);
    
    g.Free();
    end;
    
  iterator.Free();
end;

procedure initCommands();
begin
  funcList := GHashTable.Create(128);
  commandList := GHashTable.Create(128);
  commandList.setHashFunc(firstHash);
end;

procedure cleanupCommands();
begin
  funcList.clear();
  funcList.Free();

  commandList.Free();
end;

end.