/
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: progs.pas,v 1.7 2001/04/16 17:32:41 xenon Exp $

unit progs;

interface

uses
    chars,
    fsys,
    dtypes,
    area;

const MAX_IFCHECKS=20;
      IN_IF=0;
      IN_ELSE=1;
      DO_IF=2;
      DO_ELSE=3;

const COMMANDOK=0;
      IFTRUE=1;
      IFFALSE=2;
      ORTRUE=3;
      ORFALSE=4;
      FOUNDELSE=5;
      FOUNDENDIF=6;
      IFIGNORED=7;
      ORIGNORED=8;
      BERR=9;

type
    GProgram = class
      prog_type, perc : integer;
      code : string;
      args : string;
      return : boolean;

      procedure load(var fp : GFileReader; trigger : string; npc : GNPCIndex);
      function seval(lhs, opr, rhs : string; npc : GCharacter) : boolean;
      function veval(lhs : integer; opr : string; rhs : integer; npc : GCharacter) : boolean;
      function ifcheck(ifcheck : string; npc, actor : GCharacter; obj : GObject; vo : pointer; rand : GCharacter) : integer;
      function command(cmd : string; npc,actor : GCharacter; obj : GObject; ignore, ignore_ors : boolean) : integer;
      procedure driver(npc, actor : GCharacter; obj : GObject);
    end;

procedure percentCheck(npc, actor : GCharacter; obj : GObject; prog_type : integer);
procedure greetTrigger(ch : GCharacter);
procedure fightTrigger(ch, victim : GCharacter);
procedure randTrigger(ch : GCharacter);
procedure deathTrigger(ch, victim : GCharacter);
procedure resetTrigger(ch : GCharacter);
procedure actTrigger(npc, actor : GCharacter; s : string);
function blockTrigger(ch, victim : GCharacter; vnum : integer) : boolean;
procedure giveTrigger(npc, actor : GCharacter; obj : GObject);

implementation

uses
    SysUtils,
    strip,
    util,
    mudsystem,
    mudthread,
    constants;


procedure GProgram.load(var fp : GFileReader; trigger : string; npc : GNPCIndex);
var
   g : string;
begin
  g := right(uppercase(trigger),' ');
  args := right(g,' ');

  g := left(g,' ');
  perc := strtointdef(args, 0);

  if (g = 'ON_ACT') then
    begin
    prog_type := MPROG_ACT;
    SET_BIT(npc.mpflags, MPROG_ACT);
    end
  else
  if (g = 'ON_GREET') then
    begin
    prog_type := MPROG_GREET;
    SET_BIT(npc.mpflags, MPROG_GREET);
    end
  else
  if (g = 'ON_ALLGREET') then
    begin
    prog_type := MPROG_ALLGREET;
    SET_BIT(npc.mpflags, MPROG_ALLGREET);
    end
  else
  if g='ON_ENTER' then
    begin
    prog_type := MPROG_ENTER;
    SET_BIT(npc.mpflags, MPROG_ENTER);
    end
  else
  if g='ON_DEATH' then
    begin
    prog_type:=MPROG_DEATH;
    SET_BIT(npc.mpflags,MPROG_DEATH);
    end
  else
  if g='ON_BRIBE' then
    begin
    prog_type:=MPROG_BRIBE;
    SET_BIT(npc.mpflags,MPROG_BRIBE);
    end
  else
  if g='ON_FIGHT' then
    begin
    prog_type:=MPROG_FIGHT;
    SET_BIT(npc.mpflags,MPROG_FIGHT);
    end
  else
  if g='ON_RAND' then
    begin
    prog_type:=MPROG_RAND;
    SET_BIT(npc.mpflags,MPROG_RAND);
    end
  else
  if g='ON_BLOCK' then
    begin
    prog_type:=MPROG_BLOCK;
    SET_BIT(npc.mpflags,MPROG_BLOCK);
    end
  else
  if g='ON_RESET' then
    begin
    prog_type:=MPROG_RESET;
    SET_BIT(npc.mpflags,MPROG_RESET);
    end
  else
  if g='ON_GIVE' then
    begin
    prog_type:=MPROG_GIVE;
    SET_BIT(npc.mpflags,MPROG_GIVE);
    end
  else
    bugreport('GProgram.load', 'progs.pas', 'illegal trigger type', '');

  code := '';

  repeat
    g := fp.readLine;

    if (g <> '~') then
      code := code + g + #13#10;
  until (g = '~');

  npc.programs.insertLast(Self);
end;

function parseCode(prog : string; npc, actor : GCharacter; obj : GObject) : string;
var c : char;
    prog_c : integer;
    dest : string;
begin
  prog_c := 1;
  dest := '';
  Result := '';

  while (prog_c <= length(prog)) do
    begin
    c := prog[prog_c];

    if (c = '$') then
      begin
      inc(prog_c);

      case prog[prog_c] of
          'i' : dest := dest + npc.name^;
          'n' : dest := dest + actor.name^;
         else
           bugreport('parseCode', 'progs.pas', 'unknown format ' + prog[prog_c],
                     'Bad format code in this mobprog. Please check your settings.');
      end;
      end
    else
      dest := dest + c;

    inc(prog_c);
    end;

  Result := dest;
end;

{ much of the code has been taken from the mudprogs in Smaug 1.02 }
{ adapted quite a lot and removed even more, which I don't need or }
{ which is not for Grendel - Grimlord. }

function GProgram.seval(lhs, opr, rhs : string; npc : GCharacter) : boolean;
begin
  Result := false;

  if (opr = '==') then
    Result := (lhs = rhs)
  else
  if (opr='!=') then
    Result := (lhs <> rhs)
  else
    bugreport('GProgram.seval', 'progs.pas', 'invalid operator ' + opr,
              'An invalid operator was found in this mobprog. Please check your settings.');
  exit;
end;

function GProgram.veval(lhs : integer; opr : string; rhs : integer; npc : GCharacter) : boolean;
begin
  Result := false;

  if (opr='==') then
    Result := (lhs = rhs)
  else
  if (opr='!=') then
    Result := (lhs <> rhs)
  else
  if (opr='>') then
    Result := (lhs > rhs)
  else
  if (opr='<') then
    Result := (lhs < rhs)
  else
  if (opr='>=') then
    Result := (lhs >= rhs)
  else
  if (opr='<=') then
    Result := (lhs <= rhs)
  else
  if (opr='&') then
    Result := (lhs and rhs) = rhs
  else
  if (opr='|') then
    Result := (lhs or rhs) = rhs
  else
    bugreport('GProgram.veval', 'progs.pas', 'invalid operator ' + opr,
              'An invalid operator was found in this mobprog. Please check your settings.');
  exit;
end;

function GProgram.ifcheck(ifcheck : string; npc, actor : GCharacter; obj : GObject; vo : pointer; rand : GCharacter) : integer;
var cvar,chck,opr,rval : string;
    chkchar : GCharacter;
    chkobj : GObject;
begin
  ifcheck := trim(ifcheck);

  if (length(ifcheck) = 0) then
    begin
    bugreport('GProgram.ifcheck', 'mobprogs.pas', 'null ifcheck, ' + npc.name^,
              'This ifcheck does not have any parameters.');

    Result := BERR;
    exit;
    end;

  chkchar := nil;
  chkobj := nil;

  if (pos('(', ifcheck) > 0) then
    begin
    chck := left(ifcheck, '(');
    ifcheck := right(ifcheck, '(');
    end
  else
    begin
    bugreport('GProgram.ifcheck', 'mobprogs.pas', 'syntax error, ' + npc.name^,
              'Encountered a syntax error in this mobprog. Please check your settings.');
    Result := BERR;
    exit;
    end;


  if (pos(')', ifcheck) > 0) then
    begin
    cvar := left(ifcheck, ')');
    ifcheck := right(ifcheck, ')');
    end
  else
    begin
    bugreport('GProgram.ifcheck', 'mobprogs.pas', 'syntax error, ' + npc.name^,
              'Encountered a syntax error in this mobprog. Please check your settings.');

    Result := BERR;
    exit;
    end;

  ifcheck := trim(ifcheck);

  if (length(ifcheck) = 0) then
    begin
    opr := '';
    rval := '';
    end
  else
    begin
    if (pos(' ', ifcheck) > 0) then
      begin
      opr := left(ifcheck, ' ');
      ifcheck := right(ifcheck, ' ');
      end
    else
      begin
      bugreport('GProgram.ifcheck', 'mobprogs.pas', 'operator without value, ' + npc.name^,
                'Encountered a syntax error in this mobprog. Please check your settings.');
      Result := BERR;
      exit;
      end;

    rval := ifcheck;
    end;

  if (pos('$', cvar) > 0) then
    begin
    case cvar[2] of
      'i':chkchar:=npc;
      'n':chkchar:=actor;
      't':chkchar:=GCharacter(vo);
      'r':chkchar:=rand;
      'o':chkobj:=obj;
      'p':chkobj:=GObject(vo);
    else
      begin
        bugreport('GProgram.ifcheck', 'mobprogs.pas', 'bad argument, ' + npc.name^,
                  'Encountered a syntax error in this mobprog. Please check your settings.');
      Result := BERR;
      exit;
      end;
    end;
    if ((chkchar=nil) and (chkobj=nil)) then
      begin
      Result := BERR;
      exit;
      end;
  end;

  if (chck='rand') then
    begin
    Result := integer(number_percent <= strtoint(cvar));
    exit;
    end;

  if (chkchar<>nil) then
    begin
    if (chck='isimmort') then
      begin
      Result := integer(chkchar.IS_IMMORT);
      exit;
      end
    else
    if (chck='isevil') then
      begin
      Result := integer(chkchar.IS_EVIL);
      exit;
      end
    else
    if (chck='isgood') then
      begin
      Result := integer(chkchar.IS_GOOD);
      exit;
      end
    else
    if (chck='isinvis') then
      begin
      Result := integer(chkchar.IS_INVIS);
      exit;
      end
    else
    if (chck='isnpc') then
      begin
      Result := integer(chkchar.IS_NPC);
      exit;
      end
    else
    if (chck='ispc') then
      begin
      Result := integer(not chkchar.IS_NPC);
      exit;
      end
    else
    if (chck='race') then
      begin
      Result := integer(seval(chkchar.race.name,opr,rval,npc));
      exit;
      end;
    end;

  bugreport('GProgram.ifcheck', 'mobprogs.pas', 'illegal ifcheck, '+npc.name^,
            'An unknown ifcheck was found in this mobprog.');
  Result := BERR;
end;

function GProgram.command(cmd : string; npc,actor : GCharacter; obj : GObject;
                       ignore, ignore_ors : boolean) : integer;
var
    firstword, rest : string;
    validif : integer;
begin
  cmd := trim(cmd);

  firstword := left(cmd, ' ');
  rest := right(cmd, ' ');

  if (firstword = 'if') then
    begin
    if (ignore) then
      begin
      Result := IFIGNORED;
      exit;
      end
    else
      validif := ifcheck(rest, npc, actor, obj, nil, nil);

    if (validif = 1) then
      Result := IFTRUE
    else
    if (validif = 0) then
      Result := IFFALSE
    else
      Result := BERR;
    exit;
    end;

  if (firstword = 'or') then
    begin
    if (ignore_ors) then
      begin
      Result := ORIGNORED;
      exit;
      end;

    Result := BERR;
    exit;
    end;

  if (firstword = 'else') then
    begin
    Result := FOUNDELSE;
    exit;
    end;

  if (firstword ='endif') then
    begin
    Result := FOUNDENDIF;
    exit;
    end;

  if (ignore) then
    begin
    Result := COMMANDOK;
    exit;
    end;

  if (cmd = 'mpreturntrue') then
    return := true
  else
  if (cmd = 'mpreturnfalse') then
    return := false
  else
  if (cmd = 'aggrogood') then
    npc.hunting := npc.room.findRandomGood
  else
  if (cmd = 'aggroevil') then
    npc.hunting := npc.room.findRandomEvil
  else
    interpret(npc, parseCode(cmd, npc, actor, obj));

  Result := COMMANDOK;
end;

procedure GProgram.driver(npc, actor : GCharacter; obj : GObject);
var cmd, line : string;
    ifstate:array[0..MAX_IFCHECKS-1,IN_IF..DO_ELSE] of boolean;
    i, iflevel, presult, ignorelevel : integer;
    s : integer;
begin
  for iflevel:=0 to MAX_IFCHECKS-1 do
   for i:=IN_IF to DO_ELSE do
    ifstate[iflevel,i]:=false;

  return := false;
  iflevel := 0;
  ignorelevel := 0;
  cmd := code;

  repeat
    s := pos(#13#10, cmd);

    if (s > 0) then
      begin
      line := copy(cmd, 1, s - 1);
      cmd := copy(cmd, s + 2, length(cmd) - s - 2 + 1);

      presult := command(line, npc, actor, obj,
              (ifstate[iflevel,IN_IF] and (not ifstate[iflevel,DO_IF]))
              or (ifstate[iflevel,IN_ELSE] and (not ifstate[iflevel,DO_ELSE])),
              ignorelevel > 0);

      case presult of
        { command is okay, do nothing }
        COMMANDOK:;
        { encountered an ifcheck which returned true }
           IFTRUE:begin
                  inc(iflevel);
                  if iflevel>=MAX_IFCHECKS then
                    begin
                    bugreport('mprog_driver', 'mobprogs.pas', 'max number of ifs, ' + npc.name^,
                              'The maximum number of ifchecks was reached.');
                    exit;
                    end;
                  ifstate[iflevel,IN_IF]:=true;
                  ifstate[iflevel,DO_IF]:=true;
                  end;
        { encountered an ifcheck which returned false }
          IFFALSE:begin
                  inc(iflevel);
                  if iflevel>=MAX_IFCHECKS then
                    begin
                    bugreport('mprog_driver', 'mobprogs.pas', 'max number of ifs, ' + npc.name^,
                              'The maximum number of ifchecks was reached.');
                    exit;
                    end;
                  ifstate[iflevel,IN_IF]:=true;
                  ifstate[iflevel,DO_IF]:=false;
                  end;
        { encountered an or check which returned true }
           ORTRUE:begin
                  if not (ifstate[iflevel,IN_IF]) then
                    begin
                    bugreport('mprog_driver', 'mobprogs.pas', 'unmatched or - ' + npc.name^,
                              'Syntax error in this mobprog.');
                    exit;
                    end;
                  ifstate[iflevel,DO_IF]:=true;
                  end;
        { encountered an or check which returned false }
          ORFALSE:begin
                  if not (ifstate[iflevel,IN_IF]) then
                    begin
                    bugreport('mprog_driver', 'mobprogs.pas', 'unmatched or - ' + npc.name^,
                              'Syntax error in this mobprog.');
                    exit;
                    end;
                  end;
        { encountered an else block }
        FOUNDELSE:begin
                  if (ignorelevel>0) then
                    break;
                  if (ifstate[iflevel,IN_ELSE]) then
                    begin
                    bugreport('mprog_driver', 'mobprogs.pas', 'else in else block - ' + npc.name^,
                              'Syntax error in this mobprog.');
                    exit;
                    end;
                  if not (ifstate[iflevel,IN_IF]) then
                    begin
                    bugreport('mprog_driver', 'mobprogs.pas', 'unmatched else - ' + npc.name^,
                              'Syntax error in this mobprog.');
                    exit;
                    end;
                  ifstate[iflevel,IN_ELSE]:=true;
                  ifstate[iflevel,DO_ELSE]:=not ifstate[iflevel,DO_IF];
                  ifstate[iflevel,IN_IF]:=false;
                  ifstate[iflevel,DO_IF]:=false;
                  end;
        { encountered an endif block }
       FOUNDENDIF:begin
                  if not (ifstate[iflevel,IN_IF] or ifstate[iflevel,IN_ELSE]) then
                    begin
                    bugreport('mprog_driver', 'mobprogs.pas', 'unmatched endif - ' + npc.name^,
                              'Syntax error in this mobprog.');
                    exit;
                    end;
                  if (ignorelevel>0) then
                    begin
                    dec(ignorelevel);
                    break;
                    end;
                  ifstate[iflevel,IN_IF]:=false;
                  ifstate[iflevel,DO_IF]:=false;
                  ifstate[iflevel,IN_ELSE]:=false;
                  ifstate[iflevel,DO_ELSE]:=false;
                  dec(iflevel);
                  end;
        { this if block should be ignored }
        IFIGNORED:begin
                  if not (ifstate[iflevel,IN_IF] or ifstate[iflevel,IN_ELSE]) then
                    begin
                    bugreport('mprog_driver', 'mobprogs.pas', 'ignoring non-if non-else block - ' + npc.name^,
                              'Syntax error in this mobprog.');
                    exit;
                    end;
                  inc(ignorelevel);
                  end;
        { this or block should be ignored }
        ORIGNORED:begin
                  if not (ifstate[iflevel,IN_IF] or ifstate[iflevel,IN_ELSE]) then
                    begin
                    bugreport('mprog_driver', 'mobprogs.pas', 'unmatched or - ' + npc.name^,
                              'Syntax error in this mobprog.');
                    exit;
                    end;
                  if (ignorelevel=0) then
                    begin
                    bugreport('mprog_driver', 'mobprogs.pas', 'ignoring or falsely - ' + npc.name^,
                              'Syntax error in this mobprog.');
                    exit;
                    end;
                  end;
        { other error while executing }
             BERR:begin
                  bugreport('mprog_driver', 'mobprogs.pas', 'unknown error - ' + npc.name^,
                              'Syntax error in this mobprog.');
                  exit;
                  end;
      end;
      end
    else
    if (ifstate[iflevel,IN_IF]) or (ifstate[iflevel,IN_ELSE]) then
      begin
      bugreport('mprog_driver', 'mobprogs.pas', 'missing endif ' + npc.name^,
                'The ifcheck was not ended properly.');
      exit;
      end;
  until (s = 0);
end;

procedure percentCheck(npc, actor : GCharacter; obj : GObject; prog_type : integer);
var
   prg : GProgram;
   node : GListNode;
begin
  node := npc.npc_index.programs.head;

  while (node <> nil) do
    begin
    prg := node.element;

    if (prg.prog_type = prog_type) then
      begin
      if (number_percent <= prg.perc) then
        begin
        prg.driver(npc, actor, obj);

        if (prog_type <> MPROG_GREET) then
          break;
        end;
      end;

    node := node.next;
    end;
end;

procedure greetTrigger(ch : GCharacter);
var
   vmob : GCharacter;
   node : GListNode;
begin
  if (ch.IS_NPC) then
    exit;

  ch.in_command := false;

  node := ch.room.chars.head;
  while (node <> nil) do
    begin
    vmob := node.element;

    vmob.emptyBuffer;

    node := node.next;
    end;

  node := ch.room.chars.head;
  while (node <> nil) do
    begin
    vmob := node.element;

    if (not vmob.IS_NPC) or (vmob.fighting <> nil) or
     (not vmob.IS_AWAKE) or (not vmob.CAN_SEE(ch)) then
       begin
       node := node.next;
       continue;
       end;

    if (IS_SET(vmob.npc_index.mpflags, MPROG_GREET)) then
      percentCheck(vmob, ch, nil, MPROG_GREET);

    if IS_SET(vmob.act_flags,ACT_AGGRESSIVE) then
      interpret(vmob, 'growl ' + ch.name^);

    node := node.next;
    end;
end;

procedure fightTrigger(ch, victim : GCharacter);
begin
  if (not ch.IS_NPC) then
    exit;

  if (IS_SET(ch.npc_index.mpflags, MPROG_FIGHT)) then
    percentCheck(ch, victim, nil, MPROG_FIGHT);
end;

procedure randTrigger(ch : GCharacter);
var
   vict : GCharacter;
begin
  if (not ch.IS_NPC) or (ch.fighting <> nil) then
    exit;

  if (IS_SET(ch.npc_index.mpflags, MPROG_RAND)) then
    begin
    vict := ch.room.findRandomChar;

    if (vict <> nil) then
      percentCheck(ch, vict, nil, MPROG_RAND);
    end;
end;

procedure actTrigger(npc, actor : GCharacter; s : string);
var
   prg : GProgram;
   node : GListNode;
begin
  if (not npc.IS_NPC) or (npc.fighting <> nil) then
    exit;

  s := uppercase(s);

  node := npc.npc_index.programs.head;

  while (node <> nil) do
    begin
    prg := node.element;

    if (prg.prog_type = MPROG_ACT) then
      begin
      if (pos(prg.args, s) > 0) then
        prg.driver(npc, actor, nil);
      end;

    node := node.next;
    end;
end;

function blockTrigger(ch, victim : GCharacter; vnum : integer) : boolean;
var
   prg : GProgram;
   node : GListNode;
begin
  Result := false;

  if (not ch.IS_NPC) then
    exit;

  node := ch.npc_index.programs.head;

  while (node <> nil) do
    begin
    prg := node.element;

    if (prg.prog_type = MPROG_BLOCK) then
      begin
      if (strtoint(prg.args) = vnum) then
        begin
        prg.driver(ch, victim, nil);

        Result := prg.return;
        end;
      end;

    node := node.next;
    end;
end;

procedure deathTrigger(ch, victim : GCharacter);
begin
  if (not ch.IS_NPC) then
    exit;

  if (IS_SET(ch.npc_index.mpflags, MPROG_DEATH)) then
    percentCheck(ch, victim, nil, MPROG_DEATH);
end;

procedure resetTrigger(ch : GCharacter);
begin
  if (not ch.IS_NPC) then
    exit;

  if (IS_SET(ch.npc_index.mpflags, MPROG_RESET)) then
    percentCheck(ch, nil, nil, MPROG_RESET);
end;

{ GiveTrigger - Nemesis }
procedure giveTrigger(npc, actor : GCharacter; obj : GObject);
var prg : GProgram;
    node : GListNode;
begin
  if (not npc.IS_NPC) then
    exit;

  node := npc.npc_index.programs.head;

  while (node <> nil) do
    begin
    prg := node.element;

    if (prg.prog_type = MPROG_GIVE) then
      begin
      if (prg.args = uppercase(obj.name^)) then
        begin
        obj.extract;
        prg.driver(npc, actor, nil);
        end;
      end;

    node := node.next;
    end;
end;

begin
end.