/
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: mudsystem.pas,v 1.21 2001/06/06 18:59:54 xenon Exp $

unit mudsystem;

interface
uses
    Winsock2,
    SysUtils,
    Classes,
    constants,
    strip,
    clean,
    dtypes,
    util;

const BOOTTYPE_SHUTDOWN = 1;
      BOOTTYPE_REBOOT   = 2;
      BOOTTYPE_COPYOVER = 3;

type
    GTime = record
      hour, day, month, year : integer;
      sunlight : integer;
    end;

    GBoot = record
       timer : integer;
       boot_type : integer;
       started_by : pointer;
     end;

    GSystem = record
      admin_email : string;        { email address of the administration }
      mud_name : string;           { name of the MUD Grendel is serving }
      port : integer;             { port on which Grendel runs }
      log_all : boolean;          { log all player activity? }
      bind_ip : u_long;           { IP the server should bind to (when using multiple interfaces) }
      level_forcepc : integer;    { level to force players }
      level_log : integer;        { level to get log messages }
      lookup_hosts : boolean;     { lookup host names of clients? }
      deny_newconns : boolean;    { deny new connections? }
      deny_newplayers : boolean;  { disable 'CREATE', e.g. no new players }

      user_high, user_cur : integer;
     end;

     GSocial = class
       name : string;
       char_no_arg, others_no_arg: string;
       char_found, others_found, vict_found : string;
       char_auto, others_auto : string;
       char_object : string;      // Xenon (19/Feb/2001) : for objects (e.g. 'lick rapier')
       others_object : string;
     end;

     GDamMessage = class
       msg : array[1..3] of string;
       min, max : integer;
     end;

     GBattleground = record
       prize : pointer;
       lo_range, hi_range : integer;      { level range }
       winner : pointer;               { who has won the bg }
       count : integer;                   { seconds to start, -1 for running, -2 for no bg}
     end;

     GAuction = class
       item : pointer;
       seller, buyer : pointer;
       going : integer;         { 1,2, sold}
       bid : integer;
       pulse : integer;
       start : integer;

       procedure update;

       constructor Create;
     end;

var
   system_info : GSystem;
   time_info : GTime;
   boot_info : GBoot;
   bg_info : GBattleground;

   socials : GHashTable;
   dm_msg : GDLinkedList;

   clean_thread : GCleanThread;
   timer_thread : TThread;

   auction_good, auction_evil : GAuction;

   banned_masks : TStringList;


var
  OldExit : pointer;
  LogFile : textfile;

  { system data }
  BootTime : TDateTime;
  mobs_loaded:integer;
  online_time:string;
  status : THeapStatus;


const mud_booted : boolean = false;
      grace_exit : boolean = false;
      boot_type : integer = BOOTTYPE_SHUTDOWN;
      stable_system : boolean = true;


procedure write_direct(s:string);
procedure write_console(s:string);
procedure write_log(s:string);
procedure bugreport(func, pasfile, bug, desc : string);
procedure calculateonline;

procedure load_system;
procedure save_system;
function isMaskBanned(host : string) : boolean;

procedure load_damage;

procedure load_socials;

function findSocial(cmd : string) : GSocial;
function checkSocial(c : pointer; cmd, param : string) : boolean;


implementation

uses
    mudthread,
    chars,
    area,
    fsys,
    conns,
    Channels;

procedure write_direct(s : string);
begin
  write_log(s);
  writeln(s);
end;

procedure write_console(s:string);
begin
  write_log(s);

  s := FormatDateTime('[tt] ', Now) + s;

  writeln(s);

  if (mud_booted and channels_loaded) then
    to_channel(nil, s + '$7',CHANNEL_LOG,AT_LOG);
end;

procedure write_log(s:string);
begin
  s := '[' + DateTimeToStr(now) + '] [LOG] ' + s;

  if TTextRec(logfile).mode=fmOutput then
    system.writeln(logfile,s);
end;

procedure bugreport(func, pasfile, bug, desc : string);
begin
  write_console('[BUG] ' + func + ' -> ' + bug);
  write_direct('[Extended error information]');
  write_direct('Location:    function ' + func + ' in ' + pasfile);
  write_direct('Description: ' + desc);
  write_direct('');
end;

procedure calculateonline;
var tim : TDateTime;
    days, hours, minutes : integer;
begin
  tim := Now;

  days := DiffDays(BootTime, Now);
  hours := DiffHours(BootTime, Now);
  minutes := DiffMinutes(BootTime, Now);

  dec(minutes, 60 * hours);
  dec(hours, 24 * days);

  online_time := inttostr(days) + ' day(s), ' +
                 inttostr(hours) + ' hours(s), ' +
                 inttostr(minutes) + ' minutes(s)';
end;

procedure load_system;
var
   s,g : string;
   af : GFileReader;
begin
  { first some defaults }
  system_info.mud_name := 'Grendel';
  system_info.admin_email := 'admin@localhost';

  system_info.port := 4444;
  system_info.lookup_hosts := false;
  system_info.deny_newconns := false;
  system_info.deny_newplayers := false;
  system_info.level_forcepc := LEVEL_HIGHGOD;
  system_info.level_log := LEVEL_GOD;
  system_info.bind_ip := INADDR_ANY;

  try
    af := GFileReader.Create('system\sysdata.dat');
  except
    bugreport('load_system', 'mudsystem.pas', 'could not open system\sysdata.dat.',
              'The system file sysdata.dat could not be opened.');
    exit;
  end;

  repeat
    s := af.readLine;

    g := uppercase(left(s,':'));

    if g='PORT' then
      system_info.port:=strtoint(right(s,' '))
    else
    if g='NAME' then
      system_info.mud_name := right(s,' ')
    else
    if g='EMAIL' then
      system_info.admin_email := right(s,' ')
    else
    if g='HOSTLOOKUP' then
      system_info.lookup_hosts:=strtoint(right(s,' '))<>0
    else
    if g='DENYNEWCONNS' then
      system_info.deny_newconns:=strtoint(right(s,' '))<>0
    else
    if g='DENYNEWPLAYERS' then
      system_info.deny_newplayers:=strtoint(right(s,' '))<>0
    else
    if g='LEVELFORCEPC' then
      system_info.level_forcepc:=strtoint(right(s,' '))
    else
    if g='LEVELLOG' then
      system_info.level_log:=strtoint(right(s,' '))
    else
    if g='BINDIP' then
      system_info.bind_ip:=inet_addr(pchar(right(s,' ')));
  until (s = '$') or (af.eof);

  af.Free;

  try
    af := GFileReader.Create('system\bans.dat');
  except
    bugreport('load_system', 'mudsystem.pas', 'could not open system\bans.dat',
              'The system file bans.dat could not be opened.');
    exit;
  end;

  repeat
    s := af.readLine;

    if (s <> '$') then
      banned_masks.add(s);
  until (s = '$') or (af.eof);

  af.Free;
end;

procedure save_system;
var f : textfile;
    t : TInAddr;
    a : integer;
begin
  t.s_addr := system_info.bind_ip;

  assignfile(f,'system\sysdata.dat');
  rewrite(f);

  writeln(f,'Name: ',system_info.mud_name);
  writeln(f,'EMail: ',system_info.admin_email);
  writeln(f,'Port: ',system_info.port);
  writeln(f,'DenyNewConns: ',integer(system_info.deny_newconns));
  writeln(f,'DenyNewPlayers: ',integer(system_info.deny_newplayers));
  writeln(f,'HostLookup: ',integer(system_info.lookup_hosts));
  writeln(f,'LevelForcePC: ',system_info.level_forcepc);
  writeln(f,'LevelLog: ',system_info.level_log);
  writeln(f,'BindIP: ',inet_ntoa(t));
  writeln(f,'$');
  closefile(f);

  assignfile(f, 'system\bans.dat');
  rewrite(f);

  for a := 0 to banned_masks.count-1 do
    writeln(f, banned_masks[a]);

  writeln(f,'$');
  closefile(f);
end;

function isMaskBanned(host : string) : boolean;
var
   a : integer;
begin
  Result := false;

  for a := 0 to banned_masks.count-1 do
    if (StringMatches(host, banned_masks[a])) then
      begin
      Result := true;
      end;
end;

// socials
procedure load_socials;
var f : textfile;
    s, g : string;
    social : GSocial;
    line_num : integer;
begin
  assignfile(f, 'system\socials.dat');
  {$I-}
  reset(f);
  {$I+}

  if (IOResult <> 0) then
    begin
    bugreport('load_socials', 'mudsystem.pas', 'could not open system\socials.dat',
              'The system file socials.dat could not be opened.');
    exit;
    end;

  line_num := 0;

  repeat
    repeat
      readln(f,s);
      inc(line_num);
    until (uppercase(s)='#SOCIAL') or eof(f);

    if (eof(f)) then
      break;

    social := GSocial.Create;

    with social do
      repeat
      readln(f,s);
      inc(line_num);

      s := trim(s);

      g := uppercase(left(s,':'));
      
      if g = 'NAME' then
        name := uppercase(right(s,' '))
      else
      if g='CHARNOARG' then
        char_no_arg := right(s,' ')
      else
      if g='OTHERSNOARG' then
        others_no_arg := right(s,' ')
      else
      if g='CHARAUTO' then
        char_auto := right(s,' ')
      else
      if g='OTHERSAUTO' then
        others_auto := right(s,' ')
      else
      if g='CHARFOUND' then
        char_found := right(s,' ')
      else
      if g='VICTFOUND' then
        vict_found := right(s,' ')
      else
      if g='OTHERSFOUND' then
        others_found := right(s,' ')
      else
      if g='CHAROBJECT' then
        char_object := right(s,' ')
      else
      if g='OTHERSOBJECT' then
        others_object := right(s,' ');
      until (uppercase(s)='#END') or eof(f);

    if (findSocial(social.name) <> nil) then
      begin
      write_console('duplicate social "' + social.name + '" on line ' + inttostr(line_num) + ', discarding');
      social.Free;
      end
    else
      socials.put(social.name, social);
  until eof(f);

  closefile(f);
end;

function findSocial(cmd : string) : GSocial;
begin
  Result := GSocial(socials.get(cmd));
end;

{ Xenon 19/Feb/2001 :   - added socials on objects
                        - added checks on social-strings (if empty, ignore) to fix odd behaviour i noticed }
function checkSocial(c : pointer; cmd, param : string) : boolean;
var social : GSocial;
    chance : integer;
    ch, vict : GCharacter;
    obj : GObject;
    t : integer;
begin
  social := findSocial(cmd);

  if (social = nil) then
    begin
    checkSocial := false;
    exit;
    end;

  ch := GCharacter(c);

  with social do
    begin
    vict := ch.room.findChar(ch, param);
    obj := ch.room.findObject(param);
    if (obj = nil) then
      obj := ch.findEquipment(param);
    if (obj = nil) then
      obj := ch.findInventory(param);

    if (length(param)=0) then   // no victim, e.g. 'lick'
      begin
      if (length(char_no_arg) = 0) then
        ch.sendBuffer(' ')
      else
        act(AT_SOCIAL,char_no_arg,false,ch,nil,vict,TO_CHAR);

      if (length(others_no_arg) <> 0) then
        act(AT_SOCIAL,others_no_arg,false,ch,nil,vict,TO_ROOM);
      end
    else
    if vict=ch then             // victim yourself, e.g. 'lick self'
      begin
      if (length(char_auto) = 0) then
        ch.sendBuffer(' ')
      else
        act(AT_SOCIAL,char_auto,false,ch,nil,vict,TO_CHAR);
      if (length(others_auto) <> 0) then
        act(AT_SOCIAL,others_auto,false,ch,nil,vict,TO_ROOM);
      end
    else
    if (obj <> nil) then        // victim is object, e.g. 'lick rapier'
      begin
      if (length(char_object) = 0) then
        ch.sendBuffer(' ')
      else
        act(AT_SOCIAL,char_object,false,ch,obj,nil,TO_CHAR);
      if (length(others_object) <> 0) then
        act(AT_SOCIAL,others_object,false,ch,obj,nil,TO_ROOM);
      end
    else
    if vict=nil then            // victim not there, e.g. 'lick blablablabla'
      act(AT_SOCIAL,'They are not here.',false,ch,nil,nil,TO_CHAR)
    else
    begin                     // victim, e.g. 'lick grimlord'
      if (length(char_found) = 0) then
        ch.sendBuffer(' ')
      else
        act(AT_SOCIAL,char_found,false,ch,nil,vict,TO_CHAR);
      if (length(others_found) <> 0) then
        act(AT_SOCIAL,others_found,false,ch,nil,vict,TO_NOTVICT);
      if (length(vict_found) <> 0) then
        act(AT_SOCIAL,vict_found,false,ch,nil,vict,TO_VICT);

      if ((not ch.IS_NPC)) and (vict.IS_NPC) and (vict.IS_AWAKE) then
      begin
        if (ch <> vict) then
        begin
          t := GNPC(vict).context.findSymbol('onEmoteTarget');

          if (t <> -1) then
          begin
            GNPC(vict).context.push(name);
            GNPC(vict).context.push(integer(ch));   // actor
            GNPC(vict).context.push(integer(vict)); // vict
            GNPC(vict).context.setEntryPoint(t);
            GNPC(vict).context.Execute;
          end
          else
          begin
            chance:=random(10);
            case chance of
              1,2,3,4,5,6:begin
                          if (length(vict_found) <> 0) then
                            act(AT_SOCIAL,vict_found,false,vict,nil,ch,TO_VICT);
                          if (length(others_found) <> 0) then
                            act(AT_SOCIAL,others_found,false,vict,nil,ch,TO_NOTVICT);
                          if (length(char_found) = 0) then
                            ch.sendBuffer(' ')
                          else
                            act(AT_SOCIAL,char_found,false,vict,nil,ch,TO_CHAR);
                          end;
                      7,8:begin     // Xenon (19/Feb/2001) : kinda odd, this one ;)
                          interpret(vict,'say Cut it out!');
                          interpret(vict,'sigh');
                          end;
              else
                          begin
                          act(AT_SOCIAL,'$n slaps you.',false,vict,nil,ch,TO_VICT);
                          act(AT_SOCIAL,'$n slaps $N.',false,vict,nil,ch,TO_NOTVICT);
                          act(AT_SOCIAL,'You slap $N.',false,vict,nil,ch,TO_CHAR);
                          end;
            end;
          end;
        end;
      end;
    end;
    end;

  checkSocial := true;
end;

procedure load_damage;
var f:textfile;
    s:string;
    dam : GDamMessage;
begin
  assignfile(f,'system\damage.dat');
  {$I-}
  reset(f);
  {$I+}
  if IOResult<>0 then
    begin
    bugreport('load_damage', 'mudsystem.pas', 'could not open system\damage.dat',
              'The system file damage.dat could not be opened.');
    exit;
    end;

  repeat
    readln(f,s);

    dam := GDamMessage.Create;

    with dam do
      begin
      min := strtoint(left(s,' '));
      max := strtoint(right(s,' '));

      readln(f,s);
      msg[1] := s;

      readln(f,s);
      msg[2] := s;

      readln(f,s);
      msg[3] := s;
      end;

    dm_msg.insertLast(dam);

    readln(f,s);
  until eof(f);
  close(f);
end;

// GAuction
constructor GAuction.Create;
begin
  inherited Create;

  pulse := 0;
  item := nil;
  seller := nil;
  buyer := nil;
end;

procedure GAuction.update;
var
   buf : string;
begin
  inc(going);

  case going of
    1,2:begin
        if (bid > 0) then
          begin
          buf := '$B$2<Auction> $1[$7' + GCharacter(seller).name^ + '$1] $7' + cap(GObject(item).name^);

          if (going = 1) then
            buf := buf + ' $1is going ONCE to '
          else
            buf := buf + ' $1is going TWICE to ';

          buf := buf + GCharacter(buyer).name^ + ' for ' + inttostr(bid) + ' coins.';
          to_channel(GCharacter(seller),buf,CHANNEL_AUCTION,AT_REPORT);
          end
        else
          begin
          buf := '$B$2<Auction> $1[$7' + GCharacter(seller).name^ + '$1] Anyone?$7 ' + cap(GObject(item).name^) + '$1 for ' + inttostr(start) + ' coins?';
          to_channel(GCharacter(seller),buf,CHANNEL_AUCTION,AT_REPORT);
          end;
        end;
      3:begin
        if (bid > 0) then
          begin
          buf := '$B$2<Auction> $1[$7' + GCharacter(seller).name^ + '$1] $7' + cap(GObject(item).name^);

          buf := buf + ' $1has been SOLD to ' + GCharacter(buyer).name^ + ' for ' + inttostr(bid) + ' coins.';

          to_channel(GCharacter(seller),buf,CHANNEL_AUCTION,AT_REPORT);

          GObject(item).toChar(buyer);

          act(AT_REPORT,'You have won the auction! '+cap(GObject(item).name^)+' at '+
              inttostr(bid)+' coins.',false,buyer,nil,nil,TO_CHAR);

          dec(GPlayer(buyer).bankgold, bid);
          inc(GPlayer(seller).bankgold, bid);
          end
        else
          begin
          buf := '$B$2<Auction> $1[$7' + GCharacter(seller).name^ + '$1] Due to lack of bidders, auction has been halted.';

          to_channel(GCharacter(seller),buf,CHANNEL_AUCTION,AT_REPORT);

          GObject(item).toChar(seller);
          end;

        seller:=nil;
        buyer:=nil;
        item:=nil;
        end;
  end;
end;

initialization
socials := GHashTable.Create(512);
socials.setHashFunc(firstHash);
dm_msg := GDLinkedList.Create;
auction_good := GAuction.Create;
auction_evil := GAuction.Create;
banned_masks := TStringList.Create;

end.